diff --git a/.github/workflows/libretro.yml b/.github/workflows/libretro.yml index c370dd50..01aedb54 100644 --- a/.github/workflows/libretro.yml +++ b/.github/workflows/libretro.yml @@ -1,10 +1,10 @@ name: Build libretro core -# Builds cemu_libretro for every target the core currently supports and uploads -# each result as an artifact, so a release can be assembled from them. -# -# The Android jobs mirror .gitlab-ci.yml, whose android-cmake template has -# nowhere to run (this core is not on git.libretro.com). +# Builds parallel_gx2_libretro with the Makefile for every target the core +# currently supports and uploads each result as an artifact, so a release +# can be assembled from them. The dependencies are submodules built with +# the core, so a job installs no libraries beyond the system's GL and X11 +# headers on Linux. Also runs the network layer's tests. on: push: @@ -19,9 +19,6 @@ on: type: string default: all -env: - VCPKG_ROOT: "${{github.workspace}}/dependencies/vcpkg" - # Test builds can be limited to the platforms their testers use: the # platforms input (workflow_dispatch) lists them. Pushes and "all" build # everything. @@ -30,7 +27,7 @@ jobs: if: github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || contains(inputs.platforms, 'linux') name: linux-${{ matrix.arch }} runs-on: ${{ matrix.os }} - timeout-minutes: 350 + timeout-minutes: 120 strategy: fail-fast: false matrix: @@ -48,52 +45,44 @@ jobs: fetch-depth: 0 - name: "Install dependencies" + # the headers the OpenGL and Vulkan surfaces are declared with run: | sudo apt update -qq - sudo apt install -y --no-install-recommends \ - cmake ninja-build nasm autoconf automake libtool pkg-config python3 \ - libglm-dev libgl1-mesa-dev libegl1-mesa-dev freeglut3-dev \ - libx11-dev libxext-dev libxrandr-dev libxi-dev libxcursor-dev \ - libxss-dev libxkbcommon-dev libwayland-dev wayland-protocols \ - libudev-dev libdbus-1-dev libgcrypt20-dev libpulse-dev \ - libsecret-1-dev libsystemd-dev libbluetooth-dev - - - name: "Bootstrap vcpkg" - run: bash ./dependencies/vcpkg/bootstrap-vcpkg.sh - - - name: "Configure" - # Flags as documented in README.md, plus the two the GitLab job turns - # off because the core does not use them. - run: | - cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_LIBRETRO=ON \ - -DENABLE_WXWIDGETS=OFF \ - -DENABLE_DISCORD_RPC=OFF \ - -DENABLE_CUBEB=OFF \ - -DENABLE_BLUEZ=OFF \ - -DENABLE_FERAL_GAMEMODE=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON + sudo apt install -y --no-install-recommends g++-12 \ + libgl-dev libegl-dev libx11-dev libxrender-dev libx11-xcb-dev - name: "Build" - run: cmake --build build --target cemu_libretro -- -j $(nproc) + run: make -j $(nproc) CC=gcc-12 CXX=g++-12 - name: "Upload artifact" uses: actions/upload-artifact@v6 with: - name: cemu_libretro-linux-${{ matrix.arch }} - path: ./bin/cemu_libretro.so + name: parallel_gx2_libretro-linux-${{ matrix.arch }} + path: ./parallel_gx2_libretro.so + + tests: + if: github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || contains(inputs.platforms, 'linux') + name: tests + runs-on: ubuntu-22.04 + timeout-minutes: 30 + steps: + - name: "Checkout repo" + uses: actions/checkout@v6 + + - name: "curl_compat against local servers" + # plain, then under AddressSanitizer + UBSan, then ThreadSanitizer + run: | + ./dependencies/curl-compat/test/run_tests.sh + make -C dependencies/curl-compat/test clean + SANITIZER=address,undefined ./dependencies/curl-compat/test/run_tests.sh + make -C dependencies/curl-compat/test clean + SANITIZER=thread ./dependencies/curl-compat/test/run_tests.sh macos: if: github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || contains(inputs.platforms, 'macos') name: macos-${{ matrix.arch }} runs-on: macos-14 - # First macOS build of the core: src/CMakeLists.txt already keeps the X11/GL - # link line to `UNIX AND NOT APPLE` and defines VK_USE_PLATFORM_MACOS_MVK, - # but nothing here has ever been through a compiler. Same treatment as the - # Windows job until it has. - continue-on-error: true - timeout-minutes: 350 + timeout-minutes: 120 strategy: fail-fast: false matrix: @@ -106,58 +95,23 @@ jobs: submodules: "recursive" fetch-depth: 0 - - name: "Install dependencies" - run: brew install nasm automake libtool - - - name: "Install molten-vk" - # The Vulkan renderer links against the loader; on macOS that is - # MoltenVK, the release upstream's macOS build uses. - run: | - curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar - tar xf MoltenVK-macos-privateapi.tar - sudo mkdir -p /usr/local/lib - sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib - - - name: "Setup cmake" - uses: jwlawson/actions-setup-cmake@v2 - with: - cmake-version: '3.29.0' - - - name: "Bootstrap vcpkg" - run: bash ./dependencies/vcpkg/bootstrap-vcpkg.sh - - - name: "Configure" - # Same flags as the Linux job; BLUEZ and FERAL_GAMEMODE are Linux-only - # options that are already off here, and the architecture is passed the - # way the standalone macOS build passes it, so vcpkg picks the matching - # triplet for the x86_64 slice on this arm64 runner. - run: | - cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ - -DENABLE_LIBRETRO=ON \ - -DENABLE_WXWIDGETS=OFF \ - -DENABLE_DISCORD_RPC=OFF \ - -DENABLE_CUBEB=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - - name: "Build" - run: cmake --build build --target cemu_libretro -- -j $(sysctl -n hw.ncpu) + run: make -j $(sysctl -n hw.ncpu) platform=osx arch=${{ matrix.arch }} - name: "Upload artifact" uses: actions/upload-artifact@v6 with: - name: cemu_libretro-macos-${{ matrix.arch }} - path: ./bin/cemu_libretro.dylib + name: parallel_gx2_libretro-macos-${{ matrix.arch }} + path: ./parallel_gx2_libretro.dylib windows: if: github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || contains(inputs.platforms, 'windows') name: windows-x64 runs-on: windows-2022 - # The WGL path in the glue layer has never been through a compiler; give it - # a run without failing the whole workflow over it. - continue-on-error: true - timeout-minutes: 350 + timeout-minutes: 120 + defaults: + run: + shell: msys2 {0} steps: - name: "Checkout repo" @@ -166,54 +120,34 @@ jobs: submodules: "recursive" fetch-depth: 0 - - name: "Setup cmake" - uses: jwlawson/actions-setup-cmake@v2 + - name: "Set up MSYS2" + uses: msys2/setup-msys2@v2 with: - cmake-version: '3.29.0' - - - name: "Bootstrap vcpkg" - run: ./dependencies/vcpkg/bootstrap-vcpkg.bat - - - name: "Configure" - # RelWithDebInfo instead of Release: same optimization level for MSVC, - # but keeps a PDB around so a crash dump/live debug session resolves to - # real function names instead of the nearest export + a huge offset. - run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_LIBRETRO=ON -DENABLE_WXWIDGETS=OFF -DENABLE_DISCORD_RPC=OFF -DENABLE_CUBEB=OFF -DVCPKG_INSTALL_OPTIONS="--clean-after-build" + msystem: MINGW64 + install: make git mingw-w64-x86_64-gcc - name: "Build" - run: cmake --build build --target cemu_libretro --config RelWithDebInfo + run: make -j $(nproc) - name: "Upload artifact" uses: actions/upload-artifact@v6 with: - name: cemu_libretro-windows-x64 - path: ./bin/cemu_libretro.dll - - # The symbols, for reading crash offsets, apart from the core so that - # downloading the core does not mean downloading them too - - name: "Upload symbols" - uses: actions/upload-artifact@v6 - with: - name: cemu_libretro-windows-x64-pdb - path: ./bin/cemu_libretro.pdb + name: parallel_gx2_libretro-windows-x64 + path: ./parallel_gx2_libretro.dll android: if: github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || contains(inputs.platforms, 'android') name: android-${{ matrix.abi }} runs-on: ubuntu-22.04 - # This target has never been built: the whole vcpkg set has to come up for a - # cross triplet first, so do not fail the run over it. - continue-on-error: true - timeout-minutes: 350 + timeout-minutes: 120 strategy: fail-fast: false matrix: include: - abi: arm64-v8a - triplet: arm64-android + platform: android-arm64 - abi: x86_64 - triplet: x64-android + platform: android-x86_64 steps: - name: "Checkout repo" @@ -222,43 +156,12 @@ jobs: submodules: "recursive" fetch-depth: 0 - - name: "Install host dependencies" - run: | - sudo apt update -qq - sudo apt install -y --no-install-recommends \ - nasm autoconf automake libtool pkg-config python3 ninja-build - - - name: "Bootstrap vcpkg" - run: bash ./dependencies/vcpkg/bootstrap-vcpkg.sh - - - name: "Configure" - # API level 28, as in the GitLab job: bionic gained iconv (which SDL2 - # links against) and aligned_alloc there, and the template's default of - # 16 is useless for C++20 anyway. CMAKE_TOOLCHAIN_FILE is the NDK's - - # the top-level CMakeLists turns it into a vcpkg chainload toolchain. - run: | - cmake -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_LIBRETRO=ON \ - -DENABLE_BLUEZ=OFF \ - -DENABLE_FERAL_GAMEMODE=OFF \ - -DENABLE_WAYLAND=OFF \ - -DENABLE_DISCORD_RPC=OFF \ - -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ - -DVCPKG_HOST_TRIPLET=x64-linux \ - -DANDROID_PLATFORM=android-28 \ - -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake \ - -DANDROID_STL=c++_static \ - -DANDROID_ABI=${{ matrix.abi }} \ - . -Bbuild/android-${{ matrix.abi }} - - name: "Build" - run: cmake --build build/android-${{ matrix.abi }} --target cemu_libretro --config Release -- -j $(nproc) - - - name: "Prepare artifact" - run: mv bin/cemu_libretro.so cemu_libretro_android.so + # API level 28: bionic has aligned_alloc there, and C++20 wants it + run: make -j $(nproc) platform=${{ matrix.platform }} ANDROID_API=28 NDK_ROOT=$ANDROID_NDK_LATEST_HOME - name: "Upload artifact" uses: actions/upload-artifact@v6 with: - name: cemu_libretro-android-${{ matrix.abi }} - path: ./cemu_libretro_android.so + name: parallel_gx2_libretro-android-${{ matrix.abi }} + path: ./parallel_gx2_libretro_android.so diff --git a/.github/workflows/preflight-apple-embedded.yml b/.github/workflows/preflight-apple-embedded.yml index 82b79dfe..e6492730 100644 --- a/.github/workflows/preflight-apple-embedded.yml +++ b/.github/workflows/preflight-apple-embedded.yml @@ -1,14 +1,10 @@ name: Preflight Apple embedded -# Not for upstream, and not a build anyone should download yet: this is the -# first attempt at the core for iOS and tvOS, and it exists to find out what -# stops it. vcpkg has arm64-ios and arm64-tvos triplets of its own, so the -# dependency set is at least addressable; what is unknown is which of the -# twenty-odd ports the manifest asks for actually build against those SDKs, and -# what the emulator itself reaches for that is macOS rather than Apple. +# Not for upstream, and not a build anyone should download yet: the core +# for iOS and tvOS, built to find out what stops it. # # Vulkan is off - MoltenVK ships as a dylib and a loose dylib beside a core is -# not something these systems load - so the GS is Metal here, with the software +# not something these systems load - so the GPU is Metal here, with the software # readback present path the core already has for frontends that hand out no # hardware context. @@ -22,19 +18,17 @@ jobs: preflight: name: ${{ matrix.platform }} runs-on: macos-15 - timeout-minutes: 360 + timeout-minutes: 120 strategy: fail-fast: false matrix: include: - platform: ios - system_name: iOS - sysroot: iphoneos - triplet: arm64-ios + target: ios-arm64 + core: parallel_gx2_libretro_ios.dylib - platform: tvos - system_name: tvOS - sysroot: appletvos - triplet: arm64-tvos + target: tvos-arm64 + core: parallel_gx2_libretro_tvos.dylib steps: - uses: actions/checkout@v4 @@ -47,75 +41,17 @@ jobs: xcodebuild -version xcodebuild -showsdks - - name: Install the tools the ports need - run: | - brew list nasm >/dev/null 2>&1 || brew install nasm - brew list automake >/dev/null 2>&1 || brew install automake - brew list libtool >/dev/null 2>&1 || brew install libtool - - - name: Cache vcpkg binaries - uses: actions/cache@v4 - with: - path: ~/.cache/vcpkg/archives - key: vcpkg-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }} - restore-keys: vcpkg-${{ matrix.triplet }}- - - - name: Configure - run: | - # The libretro core's own options, plus what the platform decides: - # no OpenGL (these have GLES through EAGL and nothing the emulator's - # GL backend could use), no Vulkan (see the note at the top), Metal. - cmake -S . -B build/${{ matrix.platform }} \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_SYSTEM_NAME=${{ matrix.system_name }} \ - -DCMAKE_OSX_SYSROOT=${{ matrix.sysroot }} \ - -DCMAKE_OSX_ARCHITECTURES=arm64 \ - -DCMAKE_SYSTEM_PROCESSOR=arm64 \ - -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ - -DENABLE_LIBRETRO=ON \ - -DENABLE_OPENGL=OFF \ - -DENABLE_VULKAN=OFF \ - -DENABLE_METAL=ON \ - -DENABLE_DISCORD_RPC=OFF \ - -DENABLE_BLUEZ=OFF \ - -DENABLE_FERAL_GAMEMODE=OFF \ - -DENABLE_WXWIDGETS=OFF \ - -DCMAKE_MACOSX_BUNDLE=OFF - - name: Build the core - run: | - cmake --build build/${{ matrix.platform }} --target cemu_libretro --config Release -- -j $(sysctl -n hw.ncpu) + run: make -j $(sysctl -n hw.ncpu) platform=${{ matrix.target }} IOS_MINVER=13.4 MINVER=13.4 - name: What came out run: | - find build/${{ matrix.platform }} bin -name 'cemu_libretro*' -print -exec ls -l {} \; 2>/dev/null || true - CORE=$(find . -name 'cemu_libretro.dylib' | head -1) - test -n "$CORE" || { echo "no core was produced"; exit 1; } - vtool -show "$CORE" || true - otool -L "$CORE" || true - - - name: The vcpkg logs, when a port is what stopped it - if: failure() - run: | - # vcpkg writes the failing port and its logs here; the manifest log is - # mostly the manifest itself, so the error is what to grep for. - LOG=build/${{ matrix.platform }}/vcpkg-manifest-install.log - if [ -f "$LOG" ]; then - echo "=== the part of $LOG that is not the manifest" - grep -vE '^\s*[]["{},]' "$LOG" | tail -100 - fi - for f in $(find build/${{ matrix.platform }} -name 'issue_body.md' 2>/dev/null | head -2); do - echo "=== $f"; head -120 "$f" - done - # vcpkg's buildtrees live under the vcpkg root, not under the CMake - # build directory, which is why the port's own log went missing the - # first time this step ran. - for f in $(find dependencies/vcpkg/buildtrees build/${{ matrix.platform }} -name '*-err.log' -o -name '*-out.log' 2>/dev/null | head -8); do - echo "=== $f"; tail -50 "$f" - done + ls -l ${{ matrix.core }} + vtool -show ${{ matrix.core }} || true + otool -L ${{ matrix.core }} || true - uses: actions/upload-artifact@v4 if: success() with: - name: cemu_libretro_${{ matrix.platform }} - path: bin/cemu_libretro* + name: parallel_gx2_libretro_${{ matrix.platform }} + path: ${{ matrix.core }} diff --git a/.gitignore b/.gitignore index 67a268aa..4e9e5427 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,6 @@ bin/gameProfiles/* bin/graphicPacks/* # Ignore Finder view option files created by OS X -.DS_Store \ No newline at end of file +.DS_Store +# the Makefile build +/obj/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a0a2d779..3aa61d48 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,589 +7,167 @@ # Core definitions .core-defs: variables: - CORENAME: cemu - # CMake writes cemu_libretro.so to /bin (see src/CMakeLists.txt), - # which is outside of BUILD_DIR, so climb back out of build/linux-$ARCH. - EXTRA_PATH: ../../bin - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - # Cemu is C++20; the image default compiler is too old for it. - CC: gcc-12 - CXX: g++-12 - # arm64-linux is a community triplet - vcpkg ships no prebuilt tools for it - # and has to use the ones from the image. - VCPKG_FORCE_SYSTEM_BINARIES: 1 - # vcpkg builds every port from source; its binary cache keeps what it built, - # and GitLab's cache keeps that between pipelines, so a commit that leaves - # the dependencies alone does not build them again. vcpkg looks each port - # up by the hash of everything it is built from, so a cache from before a - # change of ports or of vcpkg is simply not used for what changed. The - # directory is in the repository (it has to exist before vcpkg runs). - VCPKG_DEFAULT_BINARY_CACHE: $CI_PROJECT_DIR/vcpkg-cache - cache: - key: vcpkg-$CI_JOB_NAME - paths: - - vcpkg-cache/ - # Kept from a failed job too: what it did build is still good - when: always - -variables: - GIT_SUBMODULE_STRATEGY: recursive + GIT_SUBMODULE_STRATEGY: recursive + CORENAME: parallel_gx2 # Inclusion templates, required for the build to work include: ################################## DESKTOPS ################################ - # Linux - - project: 'libretro-infrastructure/ci-templates' - file: '/linux-cmake.yml' # Windows - project: 'libretro-infrastructure/ci-templates' - file: '/windows-cmake-mingw.yml' - # macOS + file: '/windows-x64-mingw.yml' + + # Linux 64-bit - project: 'libretro-infrastructure/ci-templates' - file: '/osx-cmake-x86.yml' + file: '/linux-x64.yml' + + # Linux ARM 64-bit - project: 'libretro-infrastructure/ci-templates' - file: '/osx-cmake-arm64.yml' - # Apple embedded (iOS and tvOS, both arm64) + file: '/linux-aarch64.yml' + + # MacOS x64 - project: 'libretro-infrastructure/ci-templates' - file: '/ios-cmake.yml' + file: '/osx-x64.yml' + + # MacOS arm64 - project: 'libretro-infrastructure/ci-templates' - file: '/tvos-cmake.yml' + file: '/osx-arm64.yml' + ################################## CELLULAR ################################ # Android - project: 'libretro-infrastructure/ci-templates' - file: '/android-cmake.yml' - ################################### TELEVISION ############################# - # webOS. aarch64 only - see the job at the end for why. + file: '/android-make.yml' + + # iOS arm64 - project: 'libretro-infrastructure/ci-templates' - file: '/webos-cmake.yml' + file: '/ios-arm64.yml' + + # tvOS arm64 + - project: 'libretro-infrastructure/ci-templates' + file: '/tvos-arm64.yml' + + ################################## MISC #################################### + # webOS aarch64 + - project: 'libretro-infrastructure/ci-templates' + file: '/webos-make.yml' # Stages for building stages: - build-prepare - build-shared - -# libretro-runner-02 has been dropping jobs within seconds and then running the -# same job green on the next pipeline with nothing changed — windows-x64 after -# three 17-minute successes, both android ABIs, linux-x64. Retry rather than let -# a runner having a bad minute redden the pipeline; anything really broken still -# fails three times over. -default: - retry: 2 + - test ############################################################################## #################################### STAGES ################################## ############################################################################## # ################################### DESKTOPS ################################# -# Both Linux jobs want the same packages and the same extra time, and a job -# defining before_script replaces the template's instead of adding to it - so -# the repeated part lives here. +# Linux wants the GLX, EGL, X11 and XCB headers the core's OpenGL and +# Vulkan surfaces are declared with; a job defining before_script replaces +# the template's, so its one line is repeated here. .core-defs-linux: extends: .core-defs - # vcpkg builds every dependency from source (~30 ports, Boost among them), - # which does not fit in the default one hour. - timeout: 3h variables: - # Wayland is dead weight in a core build and the x86_64 image cannot even - # satisfy it: find_package(WaylandProtocols 1.15) fails there against the - # image's 1.13. HAS_WAYLAND only reaches wxgui - which ENABLE_LIBRETRO - # forces off - and VulkanRenderer's surface creation, which returns before - # any of it in libretro mode, where the frontend hands the core a surface. - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - -DENABLE_WAYLAND=OFF + # Cemu is C++20 + CC: gcc-12 + CXX: g++-12 before_script: - # Repeated from .libretro-linux-cmake, whose before_script this replaces. - export NUMPROC=$(($(nproc)/5)) - - if builtin type -P ccache &> /dev/null; then ccache -V &> /dev/null; fi - # libdbus-1-dev is not optional here: dependencies/vcpkg_overlay_ports_linux/dbus - # is an empty package on purpose (see the portfile), so sdl2[dbus] has only - # the system headers to build against. The rest is what the other vcpkg - # ports (SDL2, libusb) and the core itself (GL/EGL/X11) need. - apt-get update -qq - apt-get install -y --no-install-recommends - nasm autoconf automake libtool pkg-config python3 - libglm-dev libgl1-mesa-dev libegl1-mesa-dev - libx11-dev libxext-dev libxrandr-dev libxi-dev libxcursor-dev - libxss-dev libxkbcommon-dev libwayland-dev wayland-protocols - libudev-dev libdbus-1-dev libgcrypt20-dev + libgl-dev libegl-dev libx11-dev libxrender-dev libx11-xcb-dev -# Linux aarch64 -libretro-build-linux-aarch64: - extends: - - .libretro-linux-cmake-aarch64 - - .core-defs-linux - -# Linux x86_64 -libretro-build-linux-x64: - extends: - - .libretro-linux-cmake-x86_64 - - .core-defs-linux - # The template's x86_64 image is Ubuntu Xenial with GCC 9, which cannot build - # C++20 at all; backports is the same family of image with newer compilers, - # gcc-12 among them (the same one the aarch64 image is held to). - image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports - before_script: - - !reference [.core-defs-linux, before_script] - # x64-linux is a first-class triplet, so let vcpkg fetch its own cmake and - # ninja rather than holding it to whatever the image ships. That variable - # belongs to the aarch64 job, where the community triplet has no prebuilt - # tools to fetch; here it only gets in the way. - - unset VCPKG_FORCE_SYSTEM_BINARIES - -# Windows x86_64 -# Windows has only ever been built with MSVC so far (the GitHub Actions workflow -# does that on windows-2022, and the CMakeLists' Windows bits - SEH exception -# handler, /WHOLEARCHIVE, the MSVC runtime property - are written for it). This -# cross build with MXE's MinGW is new ground, on top of a vcpkg dependency set -# that has never been brought up for the mingw triplet, so keep it from failing -# the pipeline while it is worked out. +# Windows 64-bit libretro-build-windows-x64: extends: - - .libretro-windows-cmake-x86_64 + - .libretro-windows-x64-mingw-make-default - .core-defs - allow_failure: true - # Same reason as the Linux jobs: everything is built from source. - timeout: 3h - before_script: - # Repeated from .libretro-windows-cmake, whose before_script this replaces. - - export NUMPROC=$(($(nproc)/5)) - - if builtin type -P ccache &> /dev/null; then ccache -V &> /dev/null; fi - # The host here is x86_64 Linux, the same as in the Linux x64 job above. - - unset VCPKG_FORCE_SYSTEM_BINARIES - # .core-defs asks for gcc-12, a package name from the Ubuntu images. The - # compilers here come from MXE's cmake wrapper - # (x86_64-w64-mingw32.static-cmake), which the top-level CMakeLists hands to - # vcpkg as a chainload toolchain; leaving CC/CXX set would only mislead the - # ports that build with their own configure scripts. - - unset CC CXX - # What the ports configure with; the MXE image carries the cross toolchain - # and not much else. - - apt-get update -qq - - apt-get install -y --no-install-recommends - nasm autoconf automake libtool pkg-config python3 - # vcpkg builds the ports with its own scripts/toolchains/mingw.cmake (the - # chainload set in CMakeLists only covers the core's own build), and that - # file looks the compiler up as x86_64-w64-mingw32-gcc. MXE spells its - # tools x86_64-w64-mingw32.static-gcc, so the find_program() comes up empty - # and CMake falls back to the host cc/c++ - which is then handed MinGW link - # flags and dies on "ld: unrecognized option '--major-image-version'" - # before the first port is built. Give every MXE tool the name vcpkg looks - # for. - - | - for tool in /usr/lib/mxe/usr/bin/x86_64-w64-mingw32.static-*; do - [ -e "$tool" ] || continue - name=$(basename "$tool") - ln -sf "$tool" "/usr/local/bin/${name/.static-/-}" - done - variables: - # CMakeLists picks x64-windows-static as soon as WIN32 is set, which is an - # MSVC triplet; MinGW needs its own. A -D from the command line wins over - # the set(... CACHE) there, and the triplet is also what tells that code - # which port overlay to use, as WIN32 is not set yet on a Unix host. - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_WXWIDGETS=OFF - -DENABLE_DISCORD_RPC=OFF - -DENABLE_CUBEB=OFF - -DVCPKG_TARGET_TRIPLET=x64-mingw-static - -DVCPKG_HOST_TRIPLET=x64-linux -################################### CELLULAR ################################# -# Android is a fresh target for this core: unlike the Linux job it has to build -# the whole vcpkg dependency set for a cross triplet, and none of it has run -# before - so keep these from failing the pipeline while they are brought up. -.core-defs-android: - extends: .core-defs - allow_failure: true - # Same reason as the Linux job, plus a cross toolchain on top. - timeout: 3h - before_script: - # Repeated from the template, whose before_script this one replaces. - - export NUMPROC=$(($(nproc)/5)) - # vcpkg needs a host compiler for the x64-linux ports it builds first, the - # autotools most of its ports configure with, and make - the generator the - # template's cmake line falls back to; the Android image has them all - # (Ubuntu 26.04, October 2026: gcc, g++, make, ninja, autoconf, automake, - # libtool, pkg-config, python3, unzip, curl, git, nasm, zip), so nothing is - # installed here. - # .core-defs asks for gcc-12, which the Linux image has and this one does - # not. Leaving CC/CXX set to anything at all is worse than dropping them: - # the cmake ports ignore them and take the NDK's clang, but openssl builds - # with its own Configure and a makefile, which honours $CC and so feeds the - # clang flags to the host gcc ("unrecognized command-line option - # '--target=aarch64-none-linux-android28'"). Unset, openssl picks up the - # NDK clang the port puts on PATH and the host triplet - which only builds - # vcpkg's own helper tools - finds the image's cc, the same as on GitHub - # Actions, where neither variable is set to begin with. - - unset CC CXX - # The host here is x86_64, so vcpkg can fetch its own cmake and ninja - # instead of being held to the image's. That flag belongs to the aarch64 - # Linux job, where the community triplet has no prebuilt tools to fetch. - - unset VCPKG_FORCE_SYSTEM_BINARIES - # The ports are built with vcpkg's own scripts/toolchains/android.cmake - - # the chainload set in CMakeLists only covers the core's own build - and - # that file looks the NDK up in ANDROID_NDK_HOME, falling back to Windows - # paths that do not exist here. The image calls it NDK_ROOT instead, so - # "Detecting compiler hash for triplet arm64-android" dies right there. - # (GitHub Actions runners set ANDROID_NDK_HOME, hence the green build.) - - export ANDROID_NDK_HOME="$NDK_ROOT" - - export ANDROID_NDK_ROOT="$NDK_ROOT" - variables: - # The template defaults to API 16, which is nowhere near enough for C++20. - # 28 is the floor that works: bionic only got iconv (SDL2 links it) and - # aligned_alloc there. - API_LEVEL: 28 - # Cemu's CMakeLists hands the template's NDK toolchain to vcpkg as a - # chainload toolchain (see the comment there); this picks the triplet the - # ports are built for. - VCPKG_DEFAULT_TRIPLET: $VCPKG_TRIPLET - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - -DENABLE_WAYLAND=OFF - -DENABLE_DISCORD_RPC=OFF - -DVCPKG_TARGET_TRIPLET=$VCPKG_TRIPLET - -DVCPKG_HOST_TRIPLET=x64-linux - -# Android aarch64 -# macOS. Cemu ships macOS builds of its own and the core now builds for both -# slices on GitHub Actions runners, so the only unknown left is this buildbot's -# Macs: the ones pcee2 runs on have cmake, ninja, git and clang but no Homebrew, -# and several vcpkg ports want nasm or the autotools. Rather than guess, the -# setup writes down what it finds and the job keeps that as an artifact — -# traces here need an account to read, artifacts do not. -.core-defs-macos: - extends: .core-defs - allow_failure: true - # vcpkg builds every dependency from source, as on the other platforms. - timeout: 3h - before_script: - # Replaces the osx template's before_script, which uses nproc — a coreutils - # binary that a stock macOS runner does not have. - - export NUMPROC=$(($(sysctl -n hw.ncpu)/5)) - # .core-defs sets this for the arm64-linux community triplet, where vcpkg has - # no prebuilt tools to fetch. On macOS it is the opposite: holding vcpkg to - # the runner's binaries is what broke detect_compiler ("vcpkg was unable to - # detect the active compiler's information"), so let it bring its own. - - unset VCPKG_FORCE_SYSTEM_BINARIES - # .core-defs asks for gcc-12, which is a package name from the Ubuntu images; - # macOS has clang and nothing by that name, so vcpkg's detect_compiler port - # failed on "Could not find the compiler specified in the environment - # variable CC: gcc-12" before a single dependency was built. The Windows job - # unsets these for the same reason. - - unset CC CXX - # The runner's ninja is on an x86_64 Homebrew path on an arm64 machine and - # CMake could not use it; ask for Ninja only if it actually runs here. - - | - if ninja --version >/dev/null 2>&1; then GEN="-G Ninja"; else GEN=""; fi - export GEN - - | - { - echo "== $CI_JOB_NAME on $(uname -m)"; sw_vers || true; xcode-select -p || true - for t in git cmake ninja nasm brew automake autoconf libtool pkg-config python3 clang mono; do - printf '%-12s ' "$t"; command -v "$t" || echo MISSING - done - echo "generator: ${GEN:-default}"; ninja --version 2>&1 | head -1 - } > ci-macos-debug.log 2>&1 - - | - { - if command -v brew >/dev/null 2>&1; then - for pkg in nasm automake libtool; do brew list $pkg >/dev/null 2>&1 || brew install $pkg || echo "brew install $pkg FAILED"; done - else - echo "no brew on this runner - ports needing nasm or the autotools will say so below" - fi - } >> ci-macos-debug.log 2>&1 - # The Vulkan renderer links the loader; on macOS that is MoltenVK, the same - # release the standalone macOS build uses. - - | - { - curl -L -o MoltenVK-macos-privateapi.tar https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar && - tar xf MoltenVK-macos-privateapi.tar && - mkdir -p "$CI_PROJECT_DIR/deps/lib" && - cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib 2>/dev/null || - cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib "$CI_PROJECT_DIR/deps/lib" - } >> ci-macos-debug.log 2>&1 - - bash ./dependencies/vcpkg/bootstrap-vcpkg.sh 2>&1 | tail -20 | tee -a ci-macos-debug.log - # The template's script, with its output appended to the same artifact. The - # first run died here, a minute in, and the trace needs an account to read. - script: - - | - set -o pipefail - { - ${CMAKE} $GEN -DCMAKE_BUILD_TYPE=Release $CORE_ARGS "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR $CMAKE_OSX_EXTRA && - ${CMAKE} --build $BUILD_DIR --target ${CORENAME}_libretro --config Release -- -j $NUMPROC - } 2>&1 | tail -300 | tee -a ci-macos-debug.log - - mv $BUILD_DIR/$EXTRA_PATH/$LIBNAME $LIBNAME - # vcpkg reports failures by pointing at logs the trace does not show, and the - # script stops at the first failing command — so collecting them belongs here, - # where it runs whatever happened. - after_script: - - | - for f in "$BUILD_DIR/vcpkg-manifest-install.log" \ - dependencies/vcpkg/buildtrees/detect_compiler/*-err.log \ - dependencies/vcpkg/buildtrees/detect_compiler/*-out.log; do - [ -f "$f" ] || continue - echo "===== $f (tail)" >> ci-macos-debug.log - tail -60 "$f" >> ci-macos-debug.log - done - for f in $(ls -t dependencies/vcpkg/buildtrees/*/*-err.log 2>/dev/null | head -2); do - echo "===== $f (tail)" >> ci-macos-debug.log - tail -60 "$f" >> ci-macos-debug.log - done - artifacts: - when: always - expire_in: 1 week - paths: - - ci-macos-debug.log - - $LIBNAME - -# iOS and tvOS. Same core, and the platform decides three things for it: no -# OpenGL (the emulator's GL backend has nothing to run on where GL is EAGL and -# GLES), no Vulkan (MoltenVK is a dylib and these systems do not load a loose -# one), so the GS is Metal - with the readback present path the core already has -# for frontends that hand out no hardware context. -# -# JIT is the honest caveat: mapping executable pages needs an entitlement a -# stock install does not grant, which is the same bargain every JIT core on -# these platforms makes, and not something this build can fix. -# -# allow_failure while they settle, like the macOS jobs: nothing here has been -# run on a device yet, only built. -.core-defs-apple-embedded: - extends: .core-defs - allow_failure: true - # vcpkg builds every dependency from source, as on the other platforms. - timeout: 3h - variables: - # 13.4 is what CMakeLists asks for on Apple; the templates default to 8.0 - # (iOS) and 12.0 (tvOS), which is older than the C++20 library the emulator - # needs. ios-cmake.yml reads IOS_MINVER, tvos-cmake.yml reads MINVER. - IOS_MINVER: "13.4" - MINVER: "13.4" - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - -DENABLE_OPENGL=OFF - -DENABLE_VULKAN=OFF - -DENABLE_METAL=ON - -DENABLE_DISCORD_RPC=OFF - -DENABLE_WXWIDGETS=OFF - -DCMAKE_MACOSX_BUNDLE=OFF - before_script: - # Replaces the template's before_script, which is where NUMPROC is normally - # set for its `-j $NUMPROC` build line. sysctl, not nproc: a stock macOS - # runner has no coreutils. - - export NUMPROC=$(($(sysctl -n hw.ncpu)/5)) - # Same two as the macOS jobs: vcpkg must bring its own binaries, and the - # gcc-12 the Linux jobs ask for does not exist here. - - unset VCPKG_FORCE_SYSTEM_BINARIES - - unset CC CXX - - | - if command -v brew >/dev/null 2>&1; then - for pkg in nasm automake libtool; do brew list $pkg >/dev/null 2>&1 || brew install $pkg || echo "brew install $pkg FAILED"; done - fi - - bash ./dependencies/vcpkg/bootstrap-vcpkg.sh 2>&1 | tail -20 - after_script: - # vcpkg reports failures by pointing at logs the trace does not show. - - | - for f in "$BUILD_DIR/vcpkg-manifest-install.log" \ - $(ls -t dependencies/vcpkg/buildtrees/*/*-err.log 2>/dev/null | head -2); do - [ -f "$f" ] || continue - echo "===== $f (tail)" - tail -60 "$f" - done - -libretro-build-osx-x64: +# Linux 64-bit +libretro-build-linux-x64: extends: - # -x86_64, not -x86: the latter is the 32-bit profile and sets ARCH=x86. - - .libretro-osx-cmake-x86_64 - - .core-defs-macos - # The Intel runner behind the template's macosx-packaging tag only carries the - # macOS 10.15 SDK, and SDL3 needs the 11 one: UniformTypeIdentifiers.h is not - # there and GameController has no physicalInputProfile, so vcpkg cannot build - # sdl3 on it at any deployment target. Cross-compile the x86_64 slice on the - # Apple Silicon runner instead - CMakeLists.txt takes CEMU_ARCHITECTURE from - # CMAKE_OSX_ARCHITECTURES, and the GitHub macOS job builds this slice the same - # way on an arm64 runner. + - .libretro-linux-x64-make-default + - .core-defs-linux + # Ubuntu Xenial's GCC cannot build C++20; backports is the same family + # of image with newer compilers, gcc-12 among them. + image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports + +# Linux ARM 64-bit +libretro-build-linux-aarch64: + extends: + - .libretro-linux-aarch64-make-default + - .core-defs-linux + +# macOS Intel +libretro-build-osx-x64: tags: - mac-apple-silicon + extends: + - .libretro-osx-x64-make-default + - .core-defs variables: - # The template targets 10.9, where std::filesystem does not exist yet, so - # glslang stopped on "'path' is unavailable: introduced in macOS 10.15". - # Same floor the arm64 template asks for. - MACOSX_DEPLOYMENT_TARGET: "10.15" - CMAKE_OSX_EXTRA: -DCMAKE_OSX_ARCHITECTURES=x86_64 + arch: x86_64 +# macOS arm64 libretro-build-osx-arm64: extends: - - .libretro-osx-cmake-arm64 - - .core-defs-macos + - .libretro-osx-arm64-make-default + - .core-defs +################################### CELLULAR ################################# +# Android ARMv8a android-arm64-v8a: extends: - - .libretro-android-cmake-arm64-v8a - - .core-defs-android - variables: - VCPKG_TRIPLET: arm64-android + - .libretro-android-make-arm64-v8a + - .core-defs -# Android x86_64 +# Android x86_64 (for play only) android-x86_64: extends: - - .libretro-android-cmake-x86_64 - - .core-defs-android - variables: - VCPKG_TRIPLET: x64-android - -##################################### APPLE ################################## -# iOS (arm64). -################################## TELEVISION ################################ -# webOS, aarch64 only. The other half of the platform is armv7a, and the -# recompiler backends here are x86-64 and arm64 (BackendX64 and xbyak_aarch64) - -# there is nothing for a 32-bit ARM device to run Espresso code on, so that half -# is not offered rather than offered and broken. -# -# The renderer is Vulkan. Cemu's GL backend is desktop GL 4.5 with direct state -# access and the buildroot carries EGL and GLES and nothing else, so it is off -# here the same way it is on Android. The loader is dlopen()ed at run time -# (VulkanAPI.cpp) and the headers are vendored, so the build needs nothing from -# the SDK for it - but that also means the core lands on the device's own -# libvulkan, and a TV without one will load the core and fail at renderer -# creation. That is the device's property, not the build's. -# -# The dependency set is the interesting part: vcpkg builds its ports with the -# toolchain named by their triplet, not with the one CMakeLists chainloads, so -# this needs a triplet of its own (dependencies/vcpkg_triplets/arm64-webos.cmake) -# that points at the SDK's toolchain file. -# -# allow_failure while it is new: the SDK exists only on the buildbot, so the -# first pipeline is the first time this toolchain has seen the tree. -libretro-build-webos-aarch64: - allow_failure: true - extends: - - .libretro-webos-aarch64-cmake + - .libretro-android-make-x86_64 - .core-defs - # vcpkg builds every dependency from source, as on the other platforms. - timeout: 3h - variables: - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DWEBOS=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - -DENABLE_WAYLAND=OFF - -DENABLE_OPENGL=OFF - -DENABLE_VULKAN=ON - -DENABLE_DISCORD_RPC=OFF - -DENABLE_WXWIDGETS=OFF - -DENABLE_CUBEB=OFF - -DENABLE_LIBUSB=OFF - -DVCPKG_TARGET_TRIPLET=arm64-webos - -DVCPKG_HOST_TRIPLET=x64-linux - before_script: - # Repeated from the template, whose before_script this one replaces. - - export NUMPROC=$(($(nproc)/5)) - - export BUILD_DIR=build/webos-${WEBOS_ARCH} - - . "$SDK_ENV" - # The triplet reads the toolchain from the environment - see its comment. - - export WEBOS_CMAKE_TOOLCHAIN_FILE="$CMAKE_TOOLCHAIN_FILE" - # And the SDK's pkg-config, which the ports cannot find under its toolchain - # file (curl failed with "pkg-config tool not found"). - - export WEBOS_PKG_CONFIG="$(command -v pkg-config)" - # environment-setup points every one of these at the cross toolchain, which - # is right for the core and wrong for everything vcpkg builds for the host - # triplet first (its own tools, and the ports x64-linux carries). The core's - # compiler comes from the toolchain file, not from here. - - unset CC CXX CPP CFLAGS CXXFLAGS CPPFLAGS LDFLAGS AR AS LD NM RANLIB OBJCOPY OBJDUMP - # It also exports the kernel build's ARCH, CROSS_COMPILE and KERNELDIR, and - # OpenSSL's Configure puts CROSS_COMPILE in front of the compiler the triplet - # gives it - an absolute path - so every object in the openssl port failed - # with "aarch64-webos-linux-gnu-/developer/.../aarch64-webos-linux-gnu-gcc: - # not found". Nothing here builds a kernel. - - unset ARCH CROSS_COMPILE KERNELDIR - # The host is x86_64, so vcpkg can fetch its own cmake and ninja rather than - # being held to the image's. That flag belongs to the aarch64 Linux job. - - unset VCPKG_FORCE_SYSTEM_BINARIES - # This image is built around the webOS SDK and has never run vcpkg, so write - # down what it does and does not carry, and install what is missing if that - # is allowed. Not every libretro image runs as root. The notes go to the - # trace (after_script), not to an artifact: the buildbot packages every - # artifact of this job as a core. - - | - { - head -2 /etc/os-release; id - for t in git curl zip unzip tar pkg-config python3 ninja cmake make gcc g++ autoconf automake libtool nasm; do - printf '%-12s ' "$t"; command -v "$t" || echo MISSING - done - } > ci-webos-debug.log 2>&1 - - | - if [ "$(id -u)" = 0 ]; then - { - apt-get update -qq && - apt-get install -y --no-install-recommends \ - build-essential make ninja-build nasm autoconf automake libtool \ - pkg-config python3 zip unzip curl git ca-certificates - } >> ci-webos-debug.log 2>&1 || echo "apt-get failed with $?" >> ci-webos-debug.log - else - echo "not root - taking the image as it is" >> ci-webos-debug.log - fi - - bash ./dependencies/vcpkg/bootstrap-vcpkg.sh 2>&1 | tail -20 | tee -a ci-webos-debug.log - # vcpkg reports failures by pointing at logs the trace does not show. - after_script: - - | - for f in "build/webos-aarch64/vcpkg-manifest-install.log" \ - $(ls -t dependencies/vcpkg/buildtrees/*/*-err.log 2>/dev/null | head -3); do - [ -f "$f" ] || continue - echo "===== $f (tail)" >> ci-webos-debug.log - tail -60 "$f" >> ci-webos-debug.log - done - cat ci-webos-debug.log 2>/dev/null || true - artifacts: - expire_in: 1 week - paths: - - $LIBNAME -################################## APPLE EMBEDDED ############################ +# iOS arm64: Metal only, the Vulkan renderer needs MoltenVK and a +# frontend surface the core does not get here libretro-build-ios-arm64: extends: - - .libretro-ios-cmake-arm64 - - .core-defs-apple-embedded + - .libretro-ios-arm64-make-default + - .core-defs variables: - VCPKG_DEFAULT_TRIPLET: arm64-ios - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - -DENABLE_OPENGL=OFF - -DENABLE_VULKAN=OFF - -DENABLE_METAL=ON - -DENABLE_DISCORD_RPC=OFF - -DENABLE_WXWIDGETS=OFF - -DCMAKE_MACOSX_BUNDLE=OFF - -DVCPKG_TARGET_TRIPLET=arm64-ios + IOS_MINVER: "13.4" -# tvOS (arm64). +# tvOS arm64 libretro-build-tvos-arm64: extends: - - .libretro-tvos-cmake-arm64 - - .core-defs-apple-embedded + - .libretro-tvos-arm64-make-default + - .core-defs variables: - VCPKG_DEFAULT_TRIPLET: arm64-tvos - CORE_ARGS: >- - -DENABLE_LIBRETRO=ON - -DENABLE_BLUEZ=OFF - -DENABLE_FERAL_GAMEMODE=OFF - -DENABLE_OPENGL=OFF - -DENABLE_VULKAN=OFF - -DENABLE_METAL=ON - -DENABLE_DISCORD_RPC=OFF - -DENABLE_WXWIDGETS=OFF - -DCMAKE_MACOSX_BUNDLE=OFF - -DVCPKG_TARGET_TRIPLET=arm64-tvos + MINVER: "13.4" + +################################## MISC #################################### +# webOS aarch64 +libretro-build-webos-aarch64: + extends: + - .libretro-webos-aarch64-make-default + - .core-defs + +#################################### TESTS ################################### +# The network layer that stands in for libcurl and OpenSSL +# (dependencies/curl-compat), against local HTTP and TLS servers. +test-curl-compat: + stage: test + image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports + needs: [] + variables: + GIT_SUBMODULE_STRATEGY: none + CC: gcc-12 + CXX: g++-12 + before_script: + - apt-get update -qq + - apt-get install -y --no-install-recommends python3 openssl + script: + - ./dependencies/curl-compat/test/run_tests.sh + - make -C dependencies/curl-compat/test clean + - SANITIZER=address,undefined ./dependencies/curl-compat/test/run_tests.sh + - make -C dependencies/curl-compat/test clean + - SANITIZER=thread ./dependencies/curl-compat/test/run_tests.sh diff --git a/.gitmodules b/.gitmodules index 3f1863fe..d34113dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,10 +3,6 @@ url = https://github.com/WizzardSK/ZArchive shallow = true branch = vfs-file-access -[submodule "dependencies/vcpkg"] - path = dependencies/vcpkg - url = https://github.com/microsoft/vcpkg - shallow = false [submodule "dependencies/Vulkan-Headers"] path = dependencies/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers @@ -26,3 +22,24 @@ path = dependencies/cemu_graphic_packs url = https://github.com/cemu-project/cemu_graphic_packs.git shallow = true +[submodule "dependencies/fmt"] + path = dependencies/fmt + url = https://github.com/fmtlib/fmt.git +[submodule "dependencies/glm"] + path = dependencies/glm + url = https://github.com/g-truc/glm.git +[submodule "dependencies/pugixml"] + path = dependencies/pugixml + url = https://github.com/zeux/pugixml.git +[submodule "dependencies/zlib"] + path = dependencies/zlib + url = https://github.com/madler/zlib.git +[submodule "dependencies/zstd"] + path = dependencies/zstd + url = https://github.com/facebook/zstd.git +[submodule "dependencies/glslang"] + path = dependencies/glslang + url = https://github.com/KhronosGroup/glslang.git +[submodule "dependencies/libusb"] + path = dependencies/libusb + url = https://github.com/libusb/libusb.git diff --git a/.upstream-excluded b/.upstream-excluded index 64765f10..bea806b4 100644 --- a/.upstream-excluded +++ b/.upstream-excluded @@ -33,3 +33,17 @@ BUILD.md CONTRIBUTING.md CODING_STYLE.md boost.natvis +:(glob)CMakeLists.txt +:(glob)**/CMakeLists.txt +CMakePresets.json +cmake +vcpkg.json +dependencies/vcpkg +dependencies/vcpkg_overlay_ports +dependencies/vcpkg_overlay_ports_linux +dependencies/vcpkg_overlay_ports_mac +dependencies/vcpkg_overlay_ports_webos +dependencies/vcpkg_overlay_ports_win +dependencies/vcpkg_triplets +src/Cemu/Tools/DownloadManager +src/util/helpers/ZipArchive.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 423b6f60..b2e2f936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ What changed in the libretro core of this fork, newest first. Changes that come ## Unreleased -- Linux: content loaded a second time in the same RetroArch, after closing it, no longer crashes RetroArch. The core's OpenSSL read the system's `openssl.cnf`, which loads the pkcs11 engine on many distributions; that system library stays loaded when the core is unloaded and kept pointers into the first copy of the core, so the second load crashed in it. The core's OpenSSL no longer reads the system's config. +- The core is now ParaLLEl GX2, `parallel_gx2_libretro`. RetroArch keeps core options, remaps and overrides under the core's name, so they start from the defaults: to keep them, copy RetroArch's `config/Cemu/` to `config/ParaLLEl GX2/` and rename the files in it from `Cemu.*` to `ParaLLEl GX2.*`. The core's own files stay in `/Cemu/` and `/Cemu/`. +- Online play (Nintendo Network, Pretendo, SpotPass, Miiverse) goes through the HTTP and TLS client RetroArch's libretro-common has, in place of libcurl and OpenSSL: the client certificates and the RSA/AES-256-CBC suite the console's services ask for included. +- Linux: content loaded a second time in the same RetroArch, after closing it, no longer crashes RetroArch. The core's OpenSSL read the system's `openssl.cnf`, which loads the pkcs11 engine on many distributions; that system library stays loaded when the core is unloaded and kept pointers into the first copy of the core, so the second load crashed in it. The core no longer has OpenSSL at all. - A crash is no longer the core's to end: Cemu's crash handlers still write their crash log to `log.txt`, and then hand the crash to the handler there was before, where they used to end RetroArch at once (`_Exit` on Linux and Android, `exit(0)` on Windows), so the system's crash report (Android's tombstone, Windows Error Reporting) is written and names the fault. Ctrl+C and SIGTERM are left to RetroArch, which the core used to end on them (sco8487). - New options Controller > Stick Dead Zone (Player 1) and (Players 2-4), 0% to 50%, 25% by default as standalone Cemu. The GamePad's sticks had no dead zone at all, so the slightest touch, or a stick resting a hair off centre, moved the character, while a Pro Controller had Cemu's 25% (demifiend9, Smash 4); now both follow the option, and sticks that rest at the centre can go lower (NNshi). - Merged up to upstream Cemu 2.6-372 (29f2f1fa): coreinit alarm fixes and the alarm queue functions, recompiler entry speed-up and a function-scanning fix, the GamePad (DRC) audio mix fix, a GX2 flush fix and graphic pack patch-group fixes. The core reports upstream's version. diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 3b64533c..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,324 +0,0 @@ -cmake_minimum_required(VERSION 3.21.1) - -option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) -# This repository builds the libretro core and nothing else - the standalone -# wx application is gone. ENABLE_LIBRETRO stays as a variable because the rest -# of the build, much of it shared with upstream Cemu, still asks for it. -set(ENABLE_LIBRETRO ON) -option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON) - -# used by CI script to set version: -set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "") -set(EMULATOR_VERSION_MINOR "0" CACHE STRING "") -set(EMULATOR_VERSION_PATCH "0" CACHE STRING "") - -set(THREADS_PREFER_PTHREAD_FLAG true) # needs to be set before vcpkg - -execute_process( - COMMAND git log --format=%h -1 - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} - OUTPUT_VARIABLE GIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE -) -add_definitions(-DEMULATOR_HASH=${GIT_HASH}) - -# webOS is a Linux by CMAKE_SYSTEM_NAME with a buildroot as bare as Android's: -# EGL and GLES and nothing else - no X11, no Wayland, no desktop GL, no session -# bus. It has to be settled here, before the vcpkg block below picks the overlay -# ports and before the option defaults further down are chosen - which is ahead -# of project(), so the compiler is not known yet and the SDK's toolchain file is -# what gives it away (/developer/aarch64-webos-linux-gnu_sdk-buildroot/...), the -# same tell CEMU_TARGET_ANDROID uses below. -DWEBOS=ON says it outright. -if(NOT WEBOS AND CMAKE_TOOLCHAIN_FILE MATCHES "webos") - set(WEBOS TRUE) -endif() - -if (ENABLE_VCPKG) - # check if vcpkg is shallow and unshallow it if necessary - execute_process( - COMMAND git rev-parse --is-shallow-repository - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg - OUTPUT_VARIABLE is_vcpkg_shallow - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if(is_vcpkg_shallow STREQUAL "true") - message(STATUS "vcpkg is shallow. Unshallowing it now...") - execute_process( - COMMAND git fetch --unshallow - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg" - RESULT_VARIABLE result - OUTPUT_VARIABLE output - ) - endif() - - # Cross builds hand cmake a toolchain file of their own - the NDK's on - # Android, MXE's for the MinGW Windows build - which would bypass vcpkg - # entirely and leave every find_package() below unsatisfied. Hand it to - # vcpkg as a chainload toolchain instead and let vcpkg's own toolchain take - # the primary slot. (A cache entry set from the command line wins over the - # plain set() below, so this has to be done here rather than there.) - if(CMAKE_TOOLCHAIN_FILE AND NOT VCPKG_CHAINLOAD_TOOLCHAIN_FILE - AND NOT CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg.cmake$") - if(CMAKE_TOOLCHAIN_FILE MATCHES "android.toolchain.cmake$") - set(CEMU_TARGET_ANDROID ON) - endif() - set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" CACHE STRING "" FORCE) - unset(CMAKE_TOOLCHAIN_FILE CACHE) - endif() - - if(WEBOS) - # The Linux overlays take libpng, cairo and gtk3 from the system, which - # for a cross build would mean the host's x86_64 copies. Only the dbus - # one is worth carrying over - nothing links D-Bus, SDL dlopen()s it - - # and its port exists because upstream's tarball host answers 504 often - # enough to fail whole pipelines. - set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_webos;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") - # vcpkg builds its ports with the triplet's own toolchain, not with the - # one this file chainloads for the core (that reaches try_compile and - # nothing else), so the cross compiler has to be named in a triplet of - # its own. arm64-linux would build them for the host. - set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_triplets") - elseif(CEMU_TARGET_ANDROID) - # no android-specific overlay ports at the moment - vcpkg refuses to start - # if an overlay path does not exist, so do not name one that is not there - set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") - elseif(VCPKG_TARGET_TRIPLET MATCHES "mingw") - # Cross-compiling to Windows from a Unix host: WIN32 is not set yet (no - # project() call so far, and the host is what counts before it), so the - # triplet is what tells the Windows overlay apart from the Linux one. - set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_win;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") - elseif(UNIX AND NOT APPLE) - set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") - elseif(APPLE) - set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_mac;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") - else() - set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_win;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") - endif() - # libusb is a feature rather than a plain dependency because iOS and tvOS - # have no USB API to build it against: its Darwin backend is IOKit's USB - # family, and the SDK there does not even carry IOKit/IOCFBundle.h, so the - # port stops on a missing header before anything of ours is compiled. - # Nothing is lost - a phone or a TV box has no Wii U GamePad adapter to - # plug in - and ENABLE_LIBUSB below follows the same condition. - # - # This runs before project(), so APPLE is not set yet and the system name - # is what tells the platforms apart. - # webOS is left out for a different reason than the Apple ones: the port - # builds there, but its Linux backend wants libudev, which the buildroot - # does not carry - and a webOS app has no raw USB access to use it with. - if (NOT CMAKE_SYSTEM_NAME STREQUAL "iOS" AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS" AND NOT WEBOS) - list(APPEND VCPKG_MANIFEST_FEATURES "usb") - endif() - set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/vcpkg/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") - # Set this so that all the various find_package() calls don't need an explicit - # CONFIG option - set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) - if (WIN32) - set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "") - endif() -endif() - -project(Cemu VERSION 2.0.0) - -# vcpkg hangs an applocal.ps1 post-build step on every Windows target, to copy -# the DLLs it built next to the binary. That is a PowerShell script, and the -# MinGW job cross-compiles on Linux, where linking cemu_libretro.dll then ends -# in "/bin/sh: 1: powershell.exe: not found" with everything else already done. -# A static triplet has no DLLs to copy anyway. -if(WIN32 AND NOT MSVC) - set(VCPKG_APPLOCAL_DEPS OFF) -endif() - -list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -add_compile_definitions($<$:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT - -add_definitions(-DEMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR}) -add_definitions(-DEMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR}) -add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}) - -set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -# enable link time optimization for release builds -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) - -if (MSVC) - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT CemuBin) - # floating point model: precise, fiber safe optimizations - add_compile_options(/EHsc /fp:precise) - if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # Speeds up static linking (especially helpful in incremental compilation) - if((CMAKE_LINKER MATCHES ".*lld-link.*") AND (CMAKE_AR MATCHES ".*llvm-lib.*")) - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY STATIC_LIBRARY_OPTIONS /llvmlibthin) - endif() - else() - add_compile_options(/GT) - endif() - # enable additional optimization flags for release builds - add_compile_options($<$:/Oi>) # enable intrinsic functions - add_compile_options($<$:/Ot>) # favor speed -endif() - -if(CMAKE_GENERATOR STREQUAL "Xcode") - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY XCODE_STARTUP_PROJECT CemuBin) -endif() - -if (APPLE) - enable_language(OBJC OBJCXX) - set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4") -endif() - -if (UNIX AND NOT APPLE) - # webOS: the buildroot carries no Wayland. - if (WEBOS) - set(CEMU_DESKTOP_SESSION_DEFAULT OFF) - else() - set(CEMU_DESKTOP_SESSION_DEFAULT ON) - endif() - option(ENABLE_WAYLAND "Build with Wayland support" ${CEMU_DESKTOP_SESSION_DEFAULT}) -endif() - -if (APPLE) - set(ENABLE_METAL_DEFAULT ON) - set(ENABLE_OPENGL_DEFAULT OFF) -else() - set(ENABLE_METAL_DEFAULT OFF) - # The GL backend here is desktop GL 4.5 with direct state access; webOS - # offers GLES through EGL and nothing more, so Vulkan is the backend that - # fits there - the same split as Android. - if (WEBOS) - set(ENABLE_OPENGL_DEFAULT OFF) - else() - set(ENABLE_OPENGL_DEFAULT ON) - endif() -endif() - -option(ENABLE_OPENGL "Enables the OpenGL backend" ${ENABLE_OPENGL_DEFAULT}) -option(ENABLE_VULKAN "Enables the Vulkan backend" ON) -option(ENABLE_METAL "Enables the Metal backend" ${ENABLE_METAL_DEFAULT}) - -if (ENABLE_METAL AND NOT APPLE) - message(FATAL_ERROR "Metal backend is only supported on Apple platforms") -endif() - -# Input comes from the frontend (LibretroControllerProvider) and audio goes to -# it (LibretroAudioAPI); the standalone's own backends - SDL, the DSU client, -# the keyboard, real Wiimotes over hidapi/BlueZ, XInput, DirectInput, cubeb, -# XAudio2, DirectSound - are gone. libusb stays: nsyshid passes real USB toy -# portals through to the game with it, which the frontend cannot do. -# -# Off on the Apple embedded platforms, where there is no USB API to talk to and -# the dependency is not built either (see the manifest features above). -if (CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "tvOS" OR WEBOS) - set(ENABLE_LIBUSB_DEFAULT OFF) -else() - set(ENABLE_LIBUSB_DEFAULT ON) -endif() -option(ENABLE_LIBUSB "Build with libusb support" ${ENABLE_LIBUSB_DEFAULT}) - -if (ENABLE_LIBUSB) - add_compile_definitions(HAS_LIBUSB) -endif() - -if (ENABLE_LIBRETRO) - add_compile_definitions(ENABLE_LIBRETRO) - # All static libs must be compiled with -fPIC for shared library linking - set(CMAKE_POSITION_INDEPENDENT_CODE ON) -endif() - -if(ENABLE_LIBRETRO) - add_compile_definitions(RETRO_CORE) -endif() - -find_package(Threads REQUIRED) -find_package(CURL REQUIRED) -find_package(pugixml REQUIRED) -find_package(RapidJSON REQUIRED) -set(CEMU_BOOST_COMPONENTS program_options filesystem nowide) -if (ANDROID) - # Fiber/FiberAndroid.cpp needs fcontext - list(APPEND CEMU_BOOST_COMPONENTS context) -endif() -find_package(Boost COMPONENTS ${CEMU_BOOST_COMPONENTS} REQUIRED) -find_package(libzip REQUIRED) -find_package(glslang REQUIRED) -find_package(ZLIB REQUIRED) -find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available -find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED) -find_package(glm REQUIRED) -find_package(fmt 12.1 REQUIRED) -find_package(PNG REQUIRED) - -# glslang versions older than 11.11.0 define targets without a namespace -if (NOT TARGET glslang::SPIRV AND TARGET SPIRV) - add_library(glslang::SPIRV ALIAS SPIRV) -endif() - -# Android and webOS are UNIX as far as CMake is concerned, but have none of -# this: no X11 and no Wayland. -if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT WEBOS) - find_package(X11 REQUIRED) - if (ENABLE_WAYLAND) - find_package(Wayland REQUIRED Client) - find_package(WaylandScanner REQUIRED) - find_package(WaylandProtocols 1.15 REQUIRED) - - ecm_add_wayland_client_protocol(WAYLAND_PROTOCOL_SRCS - PROTOCOL "${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml" - BASENAME viewporter) - add_library(CemuWaylandProtocols STATIC ${WAYLAND_PROTOCOL_SRCS}) - target_include_directories(CemuWaylandProtocols PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") - target_include_directories(CemuWaylandProtocols PRIVATE ${Wayland_INCLUDE_DIRS}) - add_compile_definitions(HAS_WAYLAND) - endif() - -endif() - -if (WEBOS) - add_compile_definitions(WEBOS) -endif() - -if (ENABLE_VULKAN) - include_directories("dependencies/Vulkan-Headers/include") - add_compile_definitions(ENABLE_VULKAN) - add_compile_definitions(VK_NO_PROTOTYPES) -endif() - -if (ENABLE_OPENGL AND NOT ANDROID AND NOT WEBOS) - # Android and webOS have no desktop OpenGL, only GLES/EGL - the core uses - # Vulkan on both. - find_package(OpenGL REQUIRED) - add_compile_definitions(ENABLE_OPENGL) -endif() - -if (ENABLE_METAL) - include_directories(${CMAKE_SOURCE_DIR}/dependencies/metal-cpp) - - add_compile_definitions(ENABLE_METAL) -endif() - -add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) - -if (CMAKE_OSX_ARCHITECTURES) - set(CEMU_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) -else() - set(CEMU_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) -endif() -if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") - add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL) -endif() - -find_package(ZArchive QUIET) -if (NOT ZArchive_FOUND) - add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL) -endif() - -add_subdirectory(src) diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..70408d3c --- /dev/null +++ b/Makefile @@ -0,0 +1,350 @@ +DEBUG = 0 +LINK_STATIC_LIBCPLUSPLUS = 1 +LTO ?= + +CORE_DIR := . +TARGET_NAME := parallel_gx2 + +ifeq ($(ECHO),1) + E := 1 +endif + +# Windows is recognised from the environment every Windows shell sets (all +# MSYS2 environments included, whatever uname -s reports there) or from a +# MinGW-w64 compiler, so a cross build needs only CC/CXX. +ifeq ($(platform),) + platform = unix + UNAME_S := $(shell uname -s 2>/dev/null) + ifeq ($(OS),Windows_NT) + platform = win + else ifeq ($(UNAME_S),) + platform = win + else ifneq ($(findstring Darwin,$(UNAME_S)),) + platform = osx + else ifneq (,$(findstring mingw,$(shell $(CC) -dumpmachine 2>/dev/null))) + platform = win + endif +endif + +# make's built-in default compiler is cc, which MinGW-w64 does not ship. +ifneq (,$(filter win win64,$(platform))) + ifeq ($(origin CC),default) + CC := gcc + endif + ifeq ($(origin CXX),default) + CXX := g++ + endif +endif + +# webOS SDK toolchains are plain Linux cross compilers; their name is what +# tells them apart. +ifneq (,$(findstring webos,$(CC))) + ifneq (,$(findstring unix,$(platform))) + override platform := webos + endif +endif + +# The graphic packs are packed by a tool built for, and run on, the +# build machine. +ifeq ($(OS),Windows_NT) + HOSTCC ?= gcc + HOST_EXE := .exe +else + HOSTCC ?= cc +endif + +# ---------------------------------------------------------------------------- +# Target architecture: taken from the compiler (or the Apple -arch/-target +# selection), not the host, so cross builds pick the right recompiler. +# ---------------------------------------------------------------------------- +ifeq ($(platform), osx) + ifeq ($(CROSS_COMPILE),1) + arch := $(firstword $(subst -, ,$(LIBRETRO_APPLE_PLATFORM))) + endif + ifeq ($(arch),) + arch := $(shell uname -m) + endif +else ifneq (,$(filter ios-arm64 tvos-arm64 android-arm64,$(platform))) + arch := arm64 +else ifeq ($(platform), android-x86_64) + arch := x86_64 +else ifeq ($(arch),) + arch := $(firstword $(subst -, ,$(shell $(CC) -dumpmachine 2>/dev/null))) + ifeq ($(arch),) + arch := $(shell uname -m) + endif +endif + +ifneq (,$(filter x86_64 amd64 x64,$(arch))) + ARCH_X86_64 := 1 + arch := x86_64 +else ifneq (,$(filter aarch64 arm64 arm,$(arch))) + ARCH_ARM64 := 1 + arch := arm64 +else + $(error Unsupported target architecture '$(arch)': the core builds for x86_64 and arm64) +endif + +HAVE_LIBUSB := 1 +HAVE_VULKAN := 1 +HAVE_OPENGL := 0 +HAVE_METAL := 0 + +FLAGS := +CFLAGS := +CXXFLAGS := +LDFLAGS := +LIBS := + +# Unix (Linux) and webOS +ifneq (,$(filter unix webos,$(platform))) + OS_UNIX := 1 + OS_LINUX := 1 + TARGET := $(TARGET_NAME)_libretro.so + fpic := -fPIC + SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(CORE_DIR)/src/libretro/link.T -Wl,-z,noexecstack + ifeq ($(platform), webos) + FLAGS += -DWEBOS + HAVE_LIBUSB := 0 + else + # EGL alone: GL's functions are loaded by the renderer, and GLX's and + # Xlib's are found at run time (CemuLibretro.cpp), so the core loads + # without X11 + HAVE_OPENGL := 1 + LIBS += -lEGL + endif + ifeq ($(LINK_STATIC_LIBCPLUSPLUS),1) + LDFLAGS += -static-libgcc -static-libstdc++ + endif + LIBS += -lpthread -ldl -lrt + +# macOS +else ifeq ($(platform), osx) + OS_UNIX := 1 + OS_APPLE := 1 + OS_MACOS := 1 + TARGET := $(TARGET_NAME)_libretro.dylib + fpic := -fPIC + SHARED := -dynamiclib + HAVE_METAL := 1 + MACOS_MINVER ?= 13.4 + ifeq ($(CROSS_COMPILE),1) + TARGET_RULE := -target $(arch)-apple-macos$(MACOS_MINVER) -isysroot $(LIBRETRO_APPLE_ISYSROOT) + else + TARGET_RULE := -arch $(arch) -mmacosx-version-min=$(MACOS_MINVER) + endif + FLAGS += $(TARGET_RULE) + LDFLAGS += $(TARGET_RULE) -Wl,-dead_strip + LIBS += -framework AppKit -framework CoreFoundation -framework Foundation \ + -framework IOKit -framework Security -framework Metal -framework QuartzCore \ + -lobjc + +# iOS / tvOS +else ifneq (,$(filter ios-arm64 tvos-arm64,$(platform))) + OS_UNIX := 1 + OS_APPLE := 1 + OS_IOS := 1 + fpic := -fPIC + SHARED := -dynamiclib + HAVE_METAL := 1 + HAVE_VULKAN := 0 + HAVE_LIBUSB := 0 + ifeq ($(platform), tvos-arm64) + TARGET := $(TARGET_NAME)_libretro_tvos.dylib + MINVER ?= 16.4 + ifeq ($(IOSSDK),) + IOSSDK := $(shell xcrun -sdk appletvos -show-sdk-path) + endif + MINVER_FLAG := -mappletvos-version-min=$(MINVER) + else + TARGET := $(TARGET_NAME)_libretro_ios.dylib + IOS_MINVER ?= 16.4 + ifeq ($(IOSSDK),) + IOSSDK := $(shell xcrun -sdk iphoneos -show-sdk-path) + endif + MINVER_FLAG := -miphoneos-version-min=$(IOS_MINVER) + endif + CC = cc -arch arm64 -isysroot $(IOSSDK) + CXX = c++ -arch arm64 -isysroot $(IOSSDK) + FLAGS += $(MINVER_FLAG) -DIPHONEOS + LDFLAGS += $(MINVER_FLAG) + LIBS += -framework CoreFoundation -framework Foundation -framework Metal \ + -framework QuartzCore -framework IOKit -lobjc + +# Android (NDK clang) +else ifneq (,$(filter android-arm64 android-x86_64,$(platform))) + OS_UNIX := 1 + OS_ANDROID := 1 + TARGET := $(TARGET_NAME)_libretro_android.so + fpic := -fPIC + SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(CORE_DIR)/src/libretro/link.T -Wl,-z,noexecstack + ANDROID_API ?= 28 + ifeq ($(platform), android-arm64) + ANDROID_TRIPLE := aarch64-linux-android$(ANDROID_API) + else + ANDROID_TRIPLE := x86_64-linux-android$(ANDROID_API) + endif + NDK_ROOT ?= $(ANDROID_NDK_ROOT) + ANDROID_NDK_LLVM ?= $(NDK_ROOT)/toolchains/llvm/prebuilt/linux-x86_64 + CC = $(ANDROID_NDK_LLVM)/bin/clang --target=$(ANDROID_TRIPLE) + CXX = $(ANDROID_NDK_LLVM)/bin/clang++ --target=$(ANDROID_TRIPLE) + AR = $(ANDROID_NDK_LLVM)/bin/llvm-ar + FLAGS += -DANDROID -DVK_USE_PLATFORM_ANDROID_KHR -funwind-tables + LDFLAGS += -static-libstdc++ + LIBS += -landroid -llog -lm -ldl + +# Windows (MinGW-w64, native MSYS2 or cross) +else + OS_WIN := 1 + TARGET := $(TARGET_NAME)_libretro.dll + SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(CORE_DIR)/src/libretro/link.T + FLAGS += -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00 -DWIN32_LEAN_AND_MEAN + HAVE_OPENGL := 1 + LDFLAGS += -static-libgcc -static-libstdc++ -static + LIBS += -lopengl32 -ldbghelp -lgdi32 -lntdll -lpsapi -liphlpapi -lws2_32 -lbcrypt \ + -ladvapi32 -lsetupapi -lole32 -luuid -lshell32 -luser32 -lkernel32 -lwinpthread +endif + +GEN_DIR := $(CORE_DIR)/obj/gen +OBJ_DIR := $(CORE_DIR)/obj/$(platform)-$(arch) + +include Makefile.common + +# ---------------------------------------------------------------------------- +# Compiler flags +# ---------------------------------------------------------------------------- +ifneq (,$(filter 1,$(OS_LINUX) $(OS_APPLE))) + FLAGS += -D_FILE_OFFSET_BITS=64 +endif +ifeq ($(OS_APPLE),1) + FLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE +endif +FLAGS += -fno-strict-aliasing + +# Everything the core does not export stays hidden; the libretro entry points +# carry visibility("default") through RETRO_API. +ifneq (,$(fpic)) + FLAGS += $(fpic) +endif +FLAGS += -fvisibility=hidden +CXXFLAGS += -fvisibility-inlines-hidden + +ifeq ($(DEBUG), 1) + FLAGS += -O0 -g -D_DEBUG -DCEMU_DEBUG_ASSERT +else + FLAGS += -O2 -DNDEBUG +endif + +ifneq ($(LTO),) + FLAGS += $(LTO) + LDFLAGS += $(LTO) +endif + +ifneq ($(SANITIZER),) + FLAGS += -fsanitize=$(SANITIZER) + LDFLAGS += -fsanitize=$(SANITIZER) +endif + +CFLAGS := $(FLAGS) -std=gnu11 $(CFLAGS) +CXXFLAGS := $(FLAGS) -std=gnu++20 $(CXXFLAGS) +LDFLAGS += $(fpic) $(SHARED) + +# ---------------------------------------------------------------------------- +# Objects +# ---------------------------------------------------------------------------- +# Objects live under obj/-, one tree per target, so the +# submodules stay clean and a second architecture never links the first +# one's objects. +obj_of = $(patsubst %,$(OBJ_DIR)/%.o,$(1)) + +define LIB_template +$(1)_OBJECTS := $$(call obj_of,$$($(1)_SOURCES_C) $$($(1)_SOURCES_CXX) $$($(1)_SOURCES_ASM) $$($(1)_SOURCES_OBJCXX)) +$$($(1)_OBJECTS): GROUP := $(1) +OBJECTS += $$($(1)_OBJECTS) +endef + +OBJECTS := +$(foreach g,$(LIB_GROUPS),$(eval $(call LIB_template,$(g)))) +DEPS := $(OBJECTS:.o=.d) +ifneq ($(CORE_PCH),) +$(CORE_OBJECTS): $(CORE_PCH) +DEPS += $(CORE_PCH_STUB).d +endif + +# The toolchain and configuration a tree was built with; a change wipes it, +# since nothing in an object's name says which compiler produced it. +BUILD_TAG := $(CC)|$(CXX)|$(platform)|$(arch)|$(DEBUG)|$(LTO)|$(SANITIZER)|$(HAVE_VULKAN)|$(HAVE_OPENGL)|$(HAVE_METAL)|$(HAVE_LIBUSB) +BUILD_TAG_STATUS := $(shell \ + if [ "`cat $(OBJ_DIR)/.build-tag 2>/dev/null`" != "$(BUILD_TAG)" ]; then \ + rm -rf $(OBJ_DIR); mkdir -p $(OBJ_DIR); \ + echo "$(BUILD_TAG)" > $(OBJ_DIR)/.build-tag; \ + fi) + +# after the line above, which may have emptied the tree +ifneq ($(CORE_PCH),) +CORE_PCH_FLAGS_STATUS := $(shell mkdir -p $(OBJ_DIR)/pch; \ + echo '$(CORE_PCH_FLAGS)' > $(OBJ_DIR)/pch/flags.tmp; \ + cmp -s $(OBJ_DIR)/pch/flags.tmp $(OBJ_DIR)/pch/flags || mv -f $(OBJ_DIR)/pch/flags.tmp $(OBJ_DIR)/pch/flags; \ + rm -f $(OBJ_DIR)/pch/flags.tmp) +endif + +.DEFAULT_GOAL := all + +all: $(TARGET) + +-include $(DEPS) + +$(OBJECTS): | $(GEN_HEADERS) + +# The object list outgrows the 32 KiB Windows command line, so there the +# linker reads it from a response file ($(file) needs GNU make 4.0, which +# MSYS2 and the cross toolchains have; the Xcode make is 3.81). +ifeq ($(OS_WIN),1) +LINK_OBJECTS = @$(OBJ_DIR)/link-objects.rsp +else +LINK_OBJECTS = $(OBJECTS) +endif + +$(TARGET): $(OBJECTS) + @$(if $(E),echo LD $@,) +ifeq ($(OS_WIN),1) + $(file >$(OBJ_DIR)/link-objects.rsp,$(OBJECTS)) +endif + $(CXX) -o $@ $(LINK_OBJECTS) $(LDFLAGS) $(LIBS) + +$(OBJ_DIR)/%.c.o: %.c + @mkdir -p $(@D) + @$(if $(E),echo CC $<,) + $(CC) $(CFLAGS) $($(GROUP)_FLAGS) $($(GROUP)_CFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/%.cpp.o: %.cpp + @mkdir -p $(@D) + @$(if $(E),echo CXX $<,) + $(CXX) $(CXXFLAGS) $($(GROUP)_FLAGS) $($(GROUP)_CXXFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/%.cc.o: %.cc + @mkdir -p $(@D) + @$(if $(E),echo CXX $<,) + $(CXX) $(CXXFLAGS) $($(GROUP)_FLAGS) $($(GROUP)_CXXFLAGS) -MMD -MP -c $< -o $@ + +# Objective-C++ cannot take the C++ precompiled header: the header itself. +$(OBJ_DIR)/%.mm.o: %.mm + @mkdir -p $(@D) + @$(if $(E),echo OBJCXX $<,) + $(CXX) -x objective-c++ $(CXXFLAGS) $($(GROUP)_FLAGS) $(subst $(CORE_PCH_INCLUDE),$(CORE_PCH_HEADER),$($(GROUP)_CXXFLAGS)) -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/%.S.o: %.S + @mkdir -p $(@D) + @$(if $(E),echo AS $<,) + $(CC) $(CFLAGS) $($(GROUP)_FLAGS) $($(GROUP)_ASFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/%.s.o: %.s + @mkdir -p $(@D) + @$(if $(E),echo AS $<,) + $(CC) -x assembler-with-cpp $(CFLAGS) $($(GROUP)_FLAGS) $($(GROUP)_ASFLAGS) -c $< -o $@ + +clean: + rm -rf $(CORE_DIR)/obj + rm -f $(TARGET) + +.PHONY: all clean diff --git a/Makefile.common b/Makefile.common new file mode 100644 index 00000000..a214e7cc --- /dev/null +++ b/Makefile.common @@ -0,0 +1,702 @@ +# Source lists and per-library flags for the static Makefile build of the +# parallel_gx2 libretro core. Included by Makefile, which sets the platform +# switches used here: +# +# ARCH_X86_64 / ARCH_ARM64 target architecture +# OS_WIN / OS_UNIX OS family +# OS_LINUX (Linux, webOS) / OS_ANDROID / OS_APPLE, with OS_MACOS or OS_IOS +# HAVE_LIBUSB HAVE_VULKAN HAVE_OPENGL HAVE_METAL optional components +# +# Units only the standalone emulator uses are left out. + +DEP_DIR := $(CORE_DIR)/dependencies +SRC_DIR := $(CORE_DIR)/src +LRC_DIR := $(DEP_DIR)/libretro-common + +# ---------------------------------------------------------------------------- +# Generated files +# ---------------------------------------------------------------------------- +GLSLANG_DIR := $(DEP_DIR)/glslang +GLSLANG_GEN_DIR := $(GEN_DIR)/glslang +GRAPHIC_PACKS_C := $(GEN_DIR)/libretro_graphic_packs.cpp +PACK_TOOL := $(GEN_DIR)/pack_graphic_packs$(HOST_EXE) + +GEN_HEADERS := $(GLSLANG_GEN_DIR)/glslang/build_info.h $(GLSLANG_GEN_DIR)/glslang/SPIRV/GlslangToSpv.h + +# glslang's version, as its build_info.py takes it from CHANGES.md +GLSLANG_VERSION := $(shell sed -n 's/^\#\# \([0-9]*\)\.\([0-9]*\)\.\([0-9]*\).*/\1 \2 \3/p' $(GLSLANG_DIR)/CHANGES.md | head -n 1) +$(GLSLANG_GEN_DIR)/glslang/build_info.h: $(GLSLANG_DIR)/build_info.h.tmpl $(GLSLANG_DIR)/CHANGES.md + @mkdir -p $(@D) + sed -e 's/@major@/$(word 1,$(GLSLANG_VERSION))/' -e 's/@minor@/$(word 2,$(GLSLANG_VERSION))/' \ + -e 's/@patch@/$(word 3,$(GLSLANG_VERSION))/' -e 's/@flavor@//' $< > $@ + +# , where an installed glslang has the SPIRV headers +$(GLSLANG_GEN_DIR)/glslang/SPIRV/GlslangToSpv.h: + @mkdir -p $(@D) + echo '#include "$(abspath $(GLSLANG_DIR)/SPIRV/GlslangToSpv.h)"' > $@ + +# The community graphic packs, compiled in (src/libretro/LibretroGraphicPacks.cpp) +GRAPHIC_PACKS_DIR := $(DEP_DIR)/cemu_graphic_packs +PACK_TOOL_SOURCES := $(CORE_DIR)/tools/pack_graphic_packs.c \ + $(addprefix $(DEP_DIR)/zlib/,adler32.c compress.c crc32.c deflate.c trees.c zutil.c) +$(PACK_TOOL): $(PACK_TOOL_SOURCES) + @mkdir -p $(@D) + $(HOSTCC) -O2 -I$(DEP_DIR)/zlib $(PACK_TOOL_SOURCES) -o $@ +# The packs' names have spaces, which make cannot take as prerequisites: +# a stamp with a checksum of the tree's listing stands in for them, and is +# rewritten only when that changes. +GRAPHIC_PACKS_STAMP := $(GEN_DIR)/graphic_packs.stamp +GRAPHIC_PACKS_STATUS := $(shell mkdir -p $(GEN_DIR); \ + (cd $(GRAPHIC_PACKS_DIR) 2>/dev/null && ls -lR Enhancements Resolutions Mods Workarounds src LICENSE.md 2>/dev/null) \ + | cksum > $(GRAPHIC_PACKS_STAMP).tmp; \ + cmp -s $(GRAPHIC_PACKS_STAMP).tmp $(GRAPHIC_PACKS_STAMP) || mv -f $(GRAPHIC_PACKS_STAMP).tmp $(GRAPHIC_PACKS_STAMP); \ + rm -f $(GRAPHIC_PACKS_STAMP).tmp) +$(GRAPHIC_PACKS_C): $(PACK_TOOL) $(GRAPHIC_PACKS_STAMP) + @mkdir -p $(@D) + $(PACK_TOOL) $(GRAPHIC_PACKS_DIR) $@ + +# The commit the core is built from, for log.txt +GIT_HASH := $(shell git -C $(CORE_DIR) log --format=%h -1 2>/dev/null) +ifeq ($(GIT_HASH),) + GIT_HASH := unknown +endif + +# ---------------------------------------------------------------------------- +# The core +# ---------------------------------------------------------------------------- +CORE_SOURCES_C := +CORE_SOURCES_CXX := +CORE_SOURCES_ASM := +CORE_SOURCES_OBJCXX := +CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/Cafe/Account/Account.cpp \ + $(CORE_DIR)/src/Cafe/CafeSystem.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/FST/FST.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/FST/KeyCache.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/WUD/wud.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/WUHB/WUHBReader.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/fsc.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/fscDeviceHostFS.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/fscDeviceRedirect.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/fscDeviceWua.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/fscDeviceWud.cpp \ + $(CORE_DIR)/src/Cafe/Filesystem/fscDeviceWuhb.cpp \ + $(CORE_DIR)/src/Cafe/GamePatch.cpp \ + $(CORE_DIR)/src/Cafe/GameProfile/GameProfile.cpp \ + $(CORE_DIR)/src/Cafe/GraphicPack/GraphicPack2.cpp \ + $(CORE_DIR)/src/Cafe/GraphicPack/GraphicPack2Patches.cpp \ + $(CORE_DIR)/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp \ + $(CORE_DIR)/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp \ + $(CORE_DIR)/src/Cafe/HW/ACR/ACR.cpp \ + $(CORE_DIR)/src/Cafe/HW/AI/AI.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Debugger/Debugger.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterPS.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/PPCScheduler.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/PPCTimer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp \ + $(CORE_DIR)/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Common/RegisterSerializer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Common/ShaderSerializer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/FetchShader.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteBufferData.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteGSCopyShaderParser.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteIndices.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteOverlay.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteQuery.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteRingBuffer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteShader.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteSoftware.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteStreamoutGPU.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTexture.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTextureView.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteThread.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Core/LatteTiming.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib_Coord.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Renderer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/RendererCore.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/RendererShader.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/ShaderInfo/ShaderDescription.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/ShaderInfo/ShaderInstanceInfo.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Transcompiler/LatteTC.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Transcompiler/LatteTCGenIR.cpp \ + $(CORE_DIR)/src/Cafe/HW/MMU/MMU.cpp \ + $(CORE_DIR)/src/Cafe/HW/SI/SI.cpp \ + $(CORE_DIR)/src/Cafe/HW/VI/VI.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/ODM/iosu_odm.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/PDM/iosu_pdm.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/fsa/iosu_fsa.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/kernel/iosu_kernel.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_acp.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_act.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_crypto.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_fpd.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_ioctl.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_mcp.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/legacy/iosu_nim.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/nn/boss/boss_common.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/nn/boss/boss_service.cpp \ + $(CORE_DIR)/src/Cafe/IOSU/nn/iosu_nn_service.cpp \ + $(CORE_DIR)/src/Cafe/OS/RPL/COSModule.cpp \ + $(CORE_DIR)/src/Cafe/OS/RPL/elf.cpp \ + $(CORE_DIR)/src/Cafe/OS/RPL/rpl.cpp \ + $(CORE_DIR)/src/Cafe/OS/RPL/rpl_debug_symbols.cpp \ + $(CORE_DIR)/src/Cafe/OS/RPL/rpl_symbol_storage.cpp \ + $(CORE_DIR)/src/Cafe/OS/common/OSCommon.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/TCL/TCL.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/avm/avm.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/camera/camera.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_BSP.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Callbacks.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_IM.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_IOS.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/dmae/dmae.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/drmapp/drmapp.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/erreula/erreula.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_AddrTest.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Blit.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Command.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Draw.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Event.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Memory.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Misc.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Query.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Resource.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Shader.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_State.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Streamout.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Surface.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_Texture.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_TilingAperture.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/h264_avc/H264Dec.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/mic/mic.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nfc/TLV.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nfc/TagV0.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nfc/ndef.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nfc/nfc.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nfc/stream.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nlibnss/nlibnss.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_ac/nn_ac.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_acp/nn_acp.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_act/nn_act.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_aoc/nn_aoc.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_boss/nn_boss.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_ccr/nn_ccr.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_ec/nn_ec.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_fp/nn_fp.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_nim/nn_nim.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_pdm/nn_pdm.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_save/nn_save.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_sl/nn_sl.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_spm/nn_spm.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_temp/nn_temp.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nn_uds/nn_uds.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/Dimensions.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/Infinity.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/Skylander.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/Whitelist.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/g721/g721.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/nsyshid.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/nsysnet/nsysnet.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/ntag/ntag.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/padscore/padscore.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/proc_ui/proc_ui.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_aux.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_exports.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_ist.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_mix.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_multivoice.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_out.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_core/ax_voice.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/snd_user/snd_user.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/swkbd/swkbd.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/sysapp/sysapp.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/vpad/vpad.cpp \ + $(CORE_DIR)/src/Cafe/OS/libs/zlib125/zlib125.cpp \ + $(CORE_DIR)/src/Cafe/TitleList/GameInfo.cpp \ + $(CORE_DIR)/src/Cafe/TitleList/SaveInfo.cpp \ + $(CORE_DIR)/src/Cafe/TitleList/SaveList.cpp \ + $(CORE_DIR)/src/Cafe/TitleList/TitleConverter.cpp \ + $(CORE_DIR)/src/Cafe/TitleList/TitleInfo.cpp \ + $(CORE_DIR)/src/Cafe/TitleList/TitleList.cpp \ + $(CORE_DIR)/src/Cemu/ExpressionParser/ExpressionParser.cpp \ + $(CORE_DIR)/src/Cemu/FileCache/FileCache.cpp \ + $(CORE_DIR)/src/Cemu/Logging/CemuLogging.cpp \ + $(CORE_DIR)/src/Cemu/PPCAssembler/ppcAssembler.cpp \ + $(CORE_DIR)/src/Cemu/napi/napi.cpp \ + $(CORE_DIR)/src/Cemu/napi/napi_act.cpp \ + $(CORE_DIR)/src/Cemu/napi/napi_ec.cpp \ + $(CORE_DIR)/src/Cemu/napi/napi_helper.cpp \ + $(CORE_DIR)/src/Cemu/napi/napi_idbe.cpp \ + $(CORE_DIR)/src/Cemu/napi/napi_version.cpp \ + $(CORE_DIR)/src/Cemu/ncrypto/ncrypto.cpp \ + $(CORE_DIR)/src/Cemu/nex/nex.cpp \ + $(CORE_DIR)/src/Cemu/nex/nexFriends.cpp \ + $(CORE_DIR)/src/Cemu/nex/nexThread.cpp \ + $(CORE_DIR)/src/Cemu/nex/prudp.cpp \ + $(CORE_DIR)/src/Common/ExceptionHandler/Breadcrumb.cpp \ + $(CORE_DIR)/src/Common/ExceptionHandler/ExceptionHandler.cpp \ + $(CORE_DIR)/src/Common/SysAllocator.cpp \ + $(CORE_DIR)/src/Common/VFSFileStream.cpp \ + $(CORE_DIR)/src/Common/cpu_features.cpp \ + $(CORE_DIR)/src/Common/precompiled.cpp \ + $(CORE_DIR)/src/audio/IAudioAPI.cpp \ + $(CORE_DIR)/src/audio/IAudioInputAPI.cpp \ + $(CORE_DIR)/src/config/ActiveSettings.cpp \ + $(CORE_DIR)/src/config/CemuConfig.cpp \ + $(CORE_DIR)/src/config/NetworkSettings.cpp \ + $(CORE_DIR)/src/imgui/imgui_extension.cpp \ + $(CORE_DIR)/src/input/ControllerFactory.cpp \ + $(CORE_DIR)/src/input/InputManager.cpp \ + $(CORE_DIR)/src/input/api/Controller.cpp \ + $(CORE_DIR)/src/input/api/ControllerState.cpp \ + $(CORE_DIR)/src/input/api/Libretro/LibretroController.cpp \ + $(CORE_DIR)/src/input/api/Libretro/LibretroControllerProvider.cpp \ + $(CORE_DIR)/src/input/emulated/ClassicController.cpp \ + $(CORE_DIR)/src/input/emulated/EmulatedController.cpp \ + $(CORE_DIR)/src/input/emulated/ProController.cpp \ + $(CORE_DIR)/src/input/emulated/VPADController.cpp \ + $(CORE_DIR)/src/input/emulated/WPADController.cpp \ + $(CORE_DIR)/src/input/emulated/WiimoteController.cpp \ + $(CORE_DIR)/src/libretro/CemuLibretro.cpp \ + $(CORE_DIR)/src/libretro/LibretroAudioAPI.cpp \ + $(CORE_DIR)/src/libretro/LibretroGraphicPacks.cpp \ + $(CORE_DIR)/src/libretro/LibretroWindowSystem.cpp \ + $(CORE_DIR)/src/main.cpp \ + $(CORE_DIR)/src/mainLLE.cpp \ + $(CORE_DIR)/src/resource/CafeDefaultFont.cpp \ + $(CORE_DIR)/src/resource/embedded/resources.cpp \ + $(CORE_DIR)/src/util/IniParser/IniParser.cpp \ + $(CORE_DIR)/src/util/SystemInfo/SystemInfo.cpp \ + $(CORE_DIR)/src/util/VirtualHeap/VirtualHeap.cpp \ + $(CORE_DIR)/src/util/Zir/Core/IR.cpp \ + $(CORE_DIR)/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.cpp \ + $(CORE_DIR)/src/util/Zir/Passes/RegisterAllocatorForGLSL.cpp \ + $(CORE_DIR)/src/util/Zir/Passes/ZpIRRegisterAllocator.cpp \ + $(CORE_DIR)/src/util/bootSound/BootSoundReader.cpp \ + $(CORE_DIR)/src/util/crypto/aes128.cpp \ + $(CORE_DIR)/src/util/crypto/crc32.cpp \ + $(CORE_DIR)/src/util/crypto/md5.cpp \ + $(CORE_DIR)/src/util/helpers/Serializer.cpp \ + $(CORE_DIR)/src/util/helpers/helpers.cpp \ + $(CORE_DIR)/src/util/highresolutiontimer/HighResolutionTimer.cpp \ + $(CORE_DIR)/src/util/tinyxml2/tinyxml2.cpp + +CORE_SOURCES_CXX += $(GRAPHIC_PACKS_C) + +ifeq ($(OS_WIN),1) + CORE_SOURCES_CXX += \ + $(SRC_DIR)/Common/windows/platform.cpp \ + $(SRC_DIR)/Common/windows/FileStream_win32.cpp \ + $(SRC_DIR)/Common/ExceptionHandler/ExceptionHandler_win32.cpp \ + $(SRC_DIR)/util/Fiber/FiberWin.cpp \ + $(SRC_DIR)/util/MemMapper/MemMapperWin.cpp \ + $(SRC_DIR)/util/SystemInfo/SystemInfoWin.cpp +else + CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/Common/unix/platform.cpp \ + $(CORE_DIR)/src/Common/unix/FileStream_unix.cpp \ + $(CORE_DIR)/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp \ + $(CORE_DIR)/src/util/MemMapper/MemMapperUnix.cpp \ + $(CORE_DIR)/src/util/SystemInfo/SystemInfoUnix.cpp +endif +ifeq ($(OS_LINUX),1) + CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/Common/ExceptionHandler/ELFSymbolTable.cpp \ + $(CORE_DIR)/src/util/Fiber/FiberUnix.cpp \ + $(CORE_DIR)/src/util/SystemInfo/SystemInfoLinux.cpp + CORE_SOURCES_ASM += \ + $(CORE_DIR)/src/resource/embedded/fontawesome.S +endif +ifeq ($(OS_ANDROID),1) + CORE_SOURCES_CXX += \ + $(SRC_DIR)/util/Fiber/FiberAndroid.cpp \ + $(CORE_DIR)/src/util/SystemInfo/SystemInfoLinux.cpp + CORE_SOURCES_ASM += \ + $(CORE_DIR)/src/resource/embedded/fontawesome.S +endif +ifeq ($(OS_APPLE),1) + CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/util/Fiber/FiberUnix.cpp \ + $(SRC_DIR)/util/SystemInfo/SystemInfoMac.cpp + CORE_SOURCES_ASM += \ + $(SRC_DIR)/resource/embedded/fontawesome_macos.S +endif +ifeq ($(HAVE_OPENGL),1) + CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/LatteShaderGL.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp \ + $(CORE_DIR)/src/imgui/imgui_impl_opengl3.cpp +endif +ifeq ($(HAVE_VULKAN),1) + CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanQuery.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp \ + $(CORE_DIR)/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp \ + $(CORE_DIR)/src/imgui/imgui_impl_vulkan.cpp \ + $(CORE_DIR)/src/libretro/LibretroVkQueue.cpp +endif +ifeq ($(HAVE_METAL),1) + CORE_SOURCES_CXX += \ + $(addprefix $(SRC_DIR)/Cafe/HW/Latte/Renderer/Metal/, \ + CachedFBOMtl.cpp LatteTextureMtl.cpp LatteTextureReadbackMtl.cpp \ + LatteTextureViewMtl.cpp LatteToMtl.cpp MetalAttachmentsInfo.cpp \ + MetalBufferAllocator.cpp MetalCppImpl.cpp MetalDepthStencilCache.cpp \ + MetalLayerHandle.cpp MetalMemoryManager.cpp MetalOutputShaderCache.cpp \ + MetalPipelineCache.cpp MetalPipelineCompiler.cpp MetalQuery.cpp \ + MetalRenderer.cpp MetalSamplerCache.cpp MetalVoidVertexPipeline.cpp \ + RendererShaderMtl.cpp) \ + $(SRC_DIR)/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLAttrDecoder.cpp \ + $(SRC_DIR)/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSL.cpp + CORE_SOURCES_OBJCXX += \ + $(SRC_DIR)/Cafe/HW/Latte/Renderer/Metal/MetalLayer.mm \ + $(SRC_DIR)/imgui/imgui_impl_metal.mm + ifeq ($(OS_MACOS),1) + CORE_SOURCES_OBJCXX += \ + $(SRC_DIR)/Cafe/HW/Latte/Renderer/MetalView.mm \ + $(SRC_DIR)/Cafe/HW/Latte/Renderer/Vulkan/CocoaSurface.mm + endif +endif +ifeq ($(HAVE_LIBUSB),1) + CORE_SOURCES_CXX += \ + $(CORE_DIR)/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp \ + $(CORE_DIR)/src/util/libusbWrapper/libusbWrapper.cpp +endif +ifeq ($(ARCH_ARM64),1) + CORE_SOURCES_CXX += \ + $(SRC_DIR)/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp +endif + +CORE_FLAGS := -DENABLE_LIBRETRO -DRETRO_CORE -DVK_NO_PROTOTYPES \ + -DEMULATOR_VERSION_MAJOR=0 -DEMULATOR_VERSION_MINOR=0 -DEMULATOR_VERSION_PATCH=0 \ + -DEMULATOR_HASH=$(GIT_HASH) \ + -I$(SRC_DIR) -I$(SRC_DIR)/Cafe -I$(SRC_DIR)/Common -I$(SRC_DIR)/util -I$(SRC_DIR)/Cemu \ + -I$(SRC_DIR)/input -I$(SRC_DIR)/config -I$(SRC_DIR)/audio -I$(SRC_DIR)/imgui \ + -I$(SRC_DIR)/gui -I$(SRC_DIR)/gui/interface -I$(SRC_DIR)/libretro -I$(SRC_DIR)/resource \ + -I$(DEP_DIR)/Vulkan-Headers/include -I$(DEP_DIR)/ZArchive/include \ + -I$(DEP_DIR)/imgui -I$(DEP_DIR)/glm -I$(DEP_DIR)/fmt/include \ + -I$(DEP_DIR)/pugixml/src -I$(DEP_DIR)/zlib -I$(DEP_DIR)/zstd/lib \ + -I$(GLSLANG_DIR) -I$(GLSLANG_GEN_DIR) -I$(DEP_DIR)/boost-compat \ + -I$(DEP_DIR)/curl-compat/include -I$(DEP_DIR)/libusb-config \ + -I$(LRC_DIR)/include +CORE_WARNFLAGS := -Wno-multichar -Wno-invalid-offsetof -Wno-switch -Wno-ignored-attributes \ + -Wno-deprecated-enum-enum-conversion +ifeq ($(HAVE_VULKAN),1) + CORE_FLAGS += -DENABLE_VULKAN +endif +ifeq ($(HAVE_OPENGL),1) + CORE_FLAGS += -DENABLE_OPENGL +endif +ifeq ($(HAVE_METAL),1) + CORE_FLAGS += -DENABLE_METAL -DIMGUI_IMPL_METAL_CPP -I$(DEP_DIR)/metal-cpp +endif +ifeq ($(OS_APPLE),1) + CORE_FLAGS += -DVK_USE_PLATFORM_MACOS_MVK -DVK_USE_PLATFORM_METAL_EXT +endif +# desktop Linux: the X11 surfaces (Xlib and XCB headers); webOS has none +ifeq ($(OS_LINUX),1) +ifneq ($(platform),webos) + CORE_FLAGS += -DVK_USE_PLATFORM_XLIB_KHR -DVK_USE_PLATFORM_XCB_KHR +endif +endif +ifeq ($(HAVE_LIBUSB),1) + CORE_FLAGS += -DHAS_LIBUSB +endif +ifeq ($(ARCH_ARM64),1) + CORE_FLAGS += -I$(DEP_DIR)/xbyak_aarch64 -I$(DEP_DIR)/xbyak_aarch64/xbyak_aarch64 +endif +CORE_ASFLAGS := -I$(SRC_DIR)/resource + +# Every unit of the core starts with Common/precompiled.h, read +# precompiled: a stub of that name is force-included, and the compiler +# takes the precompiled header next to it in its place (GCC a .gch, which +# it skips for the stub when the flags do not match; Clang a .pch). +CORE_PCH_HEADER := $(abspath $(SRC_DIR)/Common/precompiled.h) +CORE_PCH_INCLUDE := $(CORE_PCH_HEADER) +ifneq ($(NO_PCH),1) + CORE_PCH_STUB := $(OBJ_DIR)/pch/precompiled.h + ifneq (,$(findstring clang,$(shell $(CXX) --version 2>/dev/null))) + CORE_PCH := $(CORE_PCH_STUB).pch + else + CORE_PCH := $(CORE_PCH_STUB).gch + endif + # rewritten only when the flags change, which is when the PCH must be + CORE_PCH_FLAGS = $(CXXFLAGS) $(CORE_FLAGS) $(CORE_WARNFLAGS) + $(CORE_PCH_STUB): + @mkdir -p $(@D) + echo '#include "$(CORE_PCH_HEADER)"' > $@ + $(CORE_PCH): $(CORE_PCH_STUB) $(OBJ_DIR)/pch/flags $(SRC_DIR)/Common/precompiled.h | $(GEN_HEADERS) + @$(if $(E),echo PCH $<,) + $(CXX) $(CORE_PCH_FLAGS) -MMD -MP -MF $(CORE_PCH_STUB).d -MT $@ -x c++-header -c $< -o $@ + CORE_PCH_INCLUDE := $(abspath $(CORE_PCH_STUB)) +endif +CORE_CXXFLAGS := -include $(CORE_PCH_INCLUDE) $(CORE_WARNFLAGS) + +# ---------------------------------------------------------------------------- +# libcurl's API over libretro-common's net_http, and the part of +# libretro-common the core uses +# ---------------------------------------------------------------------------- +CURLCOMPAT_SOURCES_CXX := $(DEP_DIR)/curl-compat/curl_compat.cpp +CURLCOMPAT_FLAGS := -I$(DEP_DIR)/curl-compat/include -I$(LRC_DIR)/include \ + -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_CRYPTO + +LRC_SOURCES_C := \ + $(LRC_DIR)/compat/compat_posix_string.c \ + $(LRC_DIR)/compat/compat_strcasestr.c \ + $(LRC_DIR)/compat/compat_strl.c \ + $(LRC_DIR)/compat/fopen_utf8.c \ + $(LRC_DIR)/crypto/crypto.c \ + $(LRC_DIR)/crypto/kdf.c \ + $(LRC_DIR)/crypto/pk.c \ + $(LRC_DIR)/crypto/x509.c \ + $(LRC_DIR)/encodings/encoding_base64.c \ + $(LRC_DIR)/encodings/encoding_utf.c \ + $(LRC_DIR)/features/features_cpu.c \ + $(LRC_DIR)/file/file_path.c \ + $(LRC_DIR)/file/file_path_io.c \ + $(LRC_DIR)/file/retro_dirent.c \ + $(LRC_DIR)/hash/lrc_hash.c \ + $(LRC_DIR)/lists/string_list.c \ + $(LRC_DIR)/net/net_compat.c \ + $(LRC_DIR)/net/net_http.c \ + $(LRC_DIR)/net/net_socket.c \ + $(LRC_DIR)/net/net_socket_ssl_retro.c \ + $(LRC_DIR)/rthreads/retro_eventcount.c \ + $(LRC_DIR)/rthreads/rthreads.c \ + $(LRC_DIR)/streams/file_stream.c \ + $(LRC_DIR)/string/rstrtod.c \ + $(LRC_DIR)/string/stdstring.c \ + $(LRC_DIR)/time/rtime.c \ + $(LRC_DIR)/utils/md5.c \ + $(LRC_DIR)/vfs/vfs_implementation.c +LRC_FLAGS := -I$(LRC_DIR)/include -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_CRYPTO -DHAVE_THREADS +ifeq ($(OS_ANDROID),1) + LRC_SOURCES_C += $(LRC_DIR)/libco/libco.c +endif + +# ---------------------------------------------------------------------------- +# Dependencies +# ---------------------------------------------------------------------------- +IH264D_DIR := $(DEP_DIR)/ih264d +IH264D_SOURCES_C := $(addprefix $(IH264D_DIR)/, \ + common/ih264_buf_mgr.c common/ih264_cabac_tables.c common/ih264_cavlc_tables.c common/ih264_chroma_intra_pred_filters.c common/ih264_common_tables.c common/ih264_deblk_edge_filters.c common/ih264_deblk_tables.c common/ih264_disp_mgr.c common/ih264_dpb_mgr.c common/ih264_ihadamard_scaling.c common/ih264_inter_pred_filters.c common/ih264_iquant_itrans_recon.c common/ih264_luma_intra_pred_filters.c common/ih264_mem_fns.c common/ih264_padding.c common/ih264_resi_trans_quant.c common/ih264_trans_data.c common/ih264_weighted_pred.c common/ithread.c decoder/ih264d_api.c decoder/ih264d_bitstrm.c decoder/ih264d_cabac.c decoder/ih264d_cabac_init_tables.c decoder/ih264d_compute_bs.c decoder/ih264d_deblocking.c decoder/ih264d_dpb_mgr.c decoder/ih264d_format_conv.c decoder/ih264d_function_selector_generic.c decoder/ih264d_inter_pred.c decoder/ih264d_mb_utils.c decoder/ih264d_mvpred.c decoder/ih264d_nal.c decoder/ih264d_parse_bslice.c decoder/ih264d_parse_cabac.c decoder/ih264d_parse_cavlc.c decoder/ih264d_parse_headers.c decoder/ih264d_parse_islice.c decoder/ih264d_parse_mb_header.c decoder/ih264d_parse_pslice.c decoder/ih264d_parse_slice.c decoder/ih264d_process_bslice.c decoder/ih264d_process_intra_mb.c decoder/ih264d_process_pslice.c decoder/ih264d_quant_scaling.c decoder/ih264d_sei.c decoder/ih264d_tables.c decoder/ih264d_thread_compute_bs.c decoder/ih264d_thread_parse_decode.c decoder/ih264d_utils.c decoder/ih264d_vui.c) +IH264D_SOURCES_ASM := +# ih264_list.c is left out: the decoder does not use it, and outside +# Windows it calls an ithread_usleep() that ithread.c does not have. +IH264D_FLAGS := -I$(IH264D_DIR)/common -I$(IH264D_DIR)/decoder -w +ifeq ($(ARCH_X86_64),1) + IH264D_SOURCES_C += $(addprefix $(IH264D_DIR)/, \ + 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_resi_trans_quant_sse42.c common/x86/ih264_weighted_pred_sse42.c decoder/x86/ih264d_function_selector.c decoder/x86/ih264d_function_selector_sse42.c decoder/x86/ih264d_function_selector_ssse3.c) + IH264D_FLAGS += -I$(IH264D_DIR)/common/x86 -I$(IH264D_DIR)/decoder/x86 +else + IH264D_SOURCES_C += $(addprefix $(IH264D_DIR)/, \ + decoder/arm/ih264d_function_selector_a9q.c decoder/arm/ih264d_function_selector_av8.c decoder/arm/ih264d_function_selector.c) + IH264D_SOURCES_ASM += $(addprefix $(IH264D_DIR)/, \ + common/armv8/ih264_deblk_chroma_av8.s common/armv8/ih264_deblk_luma_av8.s common/armv8/ih264_default_weighted_pred_av8.s common/armv8/ih264_ihadamard_scaling_av8.s common/armv8/ih264_inter_pred_chroma_av8.s common/armv8/ih264_inter_pred_filters_luma_horz_av8.s common/armv8/ih264_inter_pred_filters_luma_vert_av8.s common/armv8/ih264_inter_pred_luma_copy_av8.s common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s common/armv8/ih264_intra_pred_chroma_av8.s common/armv8/ih264_intra_pred_luma_16x16_av8.s common/armv8/ih264_intra_pred_luma_4x4_av8.s common/armv8/ih264_intra_pred_luma_8x8_av8.s common/armv8/ih264_iquant_itrans_recon_av8.s common/armv8/ih264_iquant_itrans_recon_dc_av8.s common/armv8/ih264_mem_fns_neon_av8.s common/armv8/ih264_padding_neon_av8.s common/armv8/ih264_resi_trans_quant_av8.s common/armv8/ih264_weighted_bi_pred_av8.s common/armv8/ih264_weighted_pred_av8.s common/armv8/macos_arm_symbol_aliases.s) + ifeq ($(OS_APPLE),1) + IH264D_SOURCES_ASM += $(IH264D_DIR)/common/armv8/macos_arm_symbol_aliases.s + endif + IH264D_FLAGS += -DARMV8 -I$(IH264D_DIR)/common/armv8 -I$(IH264D_DIR)/decoder/arm +endif +IH264D_ASFLAGS := -I$(IH264D_DIR)/common/armv8 + +ZARCHIVE_SOURCES_C := $(DEP_DIR)/ZArchive/src/sha_256.c +ZARCHIVE_SOURCES_CXX := $(DEP_DIR)/ZArchive/src/zarchivereader.cpp $(DEP_DIR)/ZArchive/src/zarchivewriter.cpp +ZARCHIVE_FLAGS := -I$(DEP_DIR)/ZArchive/include -I$(DEP_DIR)/zstd/lib -w + +IMGUI_SOURCES_CXX := $(addprefix $(DEP_DIR)/imgui/,imgui.cpp imgui_draw.cpp imgui_tables.cpp imgui_widgets.cpp) +IMGUI_FLAGS := -I$(DEP_DIR)/imgui -w + +GLSLANG_SOURCES_CXX := $(addprefix $(GLSLANG_DIR)/, \ + glslang/GenericCodeGen/CodeGen.cpp glslang/GenericCodeGen/Link.cpp glslang/MachineIndependent/glslang_tab.cpp glslang/MachineIndependent/attribute.cpp glslang/MachineIndependent/Constant.cpp glslang/MachineIndependent/iomapper.cpp glslang/MachineIndependent/InfoSink.cpp glslang/MachineIndependent/Initialize.cpp glslang/MachineIndependent/IntermTraverse.cpp glslang/MachineIndependent/Intermediate.cpp glslang/MachineIndependent/ParseContextBase.cpp glslang/MachineIndependent/ParseHelper.cpp glslang/MachineIndependent/PoolAlloc.cpp glslang/MachineIndependent/RemoveTree.cpp glslang/MachineIndependent/Scan.cpp glslang/MachineIndependent/ShaderLang.cpp glslang/MachineIndependent/SpirvIntrinsics.cpp glslang/MachineIndependent/SymbolTable.cpp glslang/MachineIndependent/Versions.cpp glslang/MachineIndependent/intermOut.cpp glslang/MachineIndependent/limits.cpp glslang/MachineIndependent/linkValidate.cpp glslang/MachineIndependent/parseConst.cpp glslang/MachineIndependent/reflection.cpp glslang/MachineIndependent/preprocessor/Pp.cpp glslang/MachineIndependent/preprocessor/PpAtom.cpp glslang/MachineIndependent/preprocessor/PpContext.cpp glslang/MachineIndependent/preprocessor/PpScanner.cpp glslang/MachineIndependent/preprocessor/PpTokens.cpp glslang/MachineIndependent/propagateNoContraction.cpp glslang/ResourceLimits/ResourceLimits.cpp SPIRV/GlslangToSpv.cpp SPIRV/InReadableOrder.cpp SPIRV/Logger.cpp SPIRV/SpvBuilder.cpp SPIRV/SpvPostProcess.cpp SPIRV/doc.cpp SPIRV/SpvTools.cpp SPIRV/disassemble.cpp) +ifeq ($(OS_WIN),1) + GLSLANG_SOURCES_CXX += $(GLSLANG_DIR)/glslang/OSDependent/Windows/ossource.cpp + GLSLANG_FLAGS := -DGLSLANG_OSINCLUDE_WIN32 +else + GLSLANG_SOURCES_CXX += $(GLSLANG_DIR)/glslang/OSDependent/Unix/ossource.cpp + GLSLANG_FLAGS := -DGLSLANG_OSINCLUDE_UNIX +endif +GLSLANG_FLAGS += -DENABLE_SPIRV -DENABLE_OPT=0 -I$(GLSLANG_DIR) -I$(GLSLANG_GEN_DIR) -w + +# fmt is header-only except on Android (precompiled.h): format.cc there only, +# as its definitions and the inline ones every other TU has would clash where +# the linker does not merge them (PE/COFF) +FMT_SOURCES_CXX := $(DEP_DIR)/fmt/src/os.cc +FMT_FLAGS := -I$(DEP_DIR)/fmt/include -w +ifeq ($(OS_ANDROID),1) + FMT_SOURCES_CXX += $(DEP_DIR)/fmt/src/format.cc +else + FMT_FLAGS += -DFMT_HEADER_ONLY +endif + +PUGIXML_SOURCES_CXX := $(DEP_DIR)/pugixml/src/pugixml.cpp +PUGIXML_FLAGS := -w + +ZLIB_SOURCES_C := $(addprefix $(DEP_DIR)/zlib/,adler32.c compress.c crc32.c deflate.c gzclose.c gzlib.c gzread.c gzwrite.c infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c) +ZLIB_FLAGS := -w +ifneq ($(OS_WIN),1) + ZLIB_FLAGS += -DHAVE_UNISTD_H +endif + +ZSTD_SOURCES_C := $(addprefix $(DEP_DIR)/zstd/lib/, \ + common/debug.c common/entropy_common.c common/error_private.c common/fse_decompress.c common/pool.c common/threading.c common/xxhash.c common/zstd_common.c compress/fse_compress.c compress/hist.c compress/huf_compress.c compress/zstd_compress.c compress/zstd_compress_literals.c compress/zstd_compress_sequences.c compress/zstd_compress_superblock.c compress/zstd_double_fast.c compress/zstd_fast.c compress/zstd_lazy.c compress/zstd_ldm.c compress/zstd_opt.c compress/zstd_preSplit.c compress/zstdmt_compress.c decompress/huf_decompress.c decompress/zstd_ddict.c decompress/zstd_decompress.c decompress/zstd_decompress_block.c) +ZSTD_SOURCES_ASM := +ZSTD_FLAGS := -I$(DEP_DIR)/zstd/lib -I$(DEP_DIR)/zstd/lib/common -DZSTD_MULTITHREAD -w +ifeq ($(ARCH_X86_64),1) + ifneq ($(OS_WIN),1) + ZSTD_SOURCES_ASM += $(DEP_DIR)/zstd/lib/decompress/huf_decompress_amd64.S + else + ZSTD_FLAGS += -DZSTD_DISABLE_ASM + endif +endif + +LIBUSB_DIR := $(DEP_DIR)/libusb/libusb +LIBUSB_SOURCES_C := $(addprefix $(LIBUSB_DIR)/,core.c descriptor.c hotplug.c io.c strerror.c sync.c) +ifneq (,$(filter 1,$(OS_LINUX) $(OS_ANDROID))) + LIBUSB_SOURCES_C += $(LIBUSB_DIR)/os/linux_usbfs.c $(LIBUSB_DIR)/os/linux_netlink.c +endif +ifeq ($(OS_UNIX),1) + LIBUSB_SOURCES_C += $(LIBUSB_DIR)/os/events_posix.c $(LIBUSB_DIR)/os/threads_posix.c +endif +ifeq ($(OS_WIN),1) + LIBUSB_SOURCES_C += $(addprefix $(LIBUSB_DIR)/os/,threads_windows.c windows_common.c \ + windows_usbdk.c windows_winusb.c events_windows.c) +endif +ifeq ($(OS_MACOS),1) + LIBUSB_SOURCES_C += $(LIBUSB_DIR)/os/darwin_usb.c +endif +LIBUSB_FLAGS := -I$(DEP_DIR)/libusb-config -I$(LIBUSB_DIR) -w + +XBYAK_SOURCES_CXX := $(DEP_DIR)/xbyak_aarch64/src/xbyak_aarch64_impl.cpp $(DEP_DIR)/xbyak_aarch64/src/util_impl.cpp +XBYAK_FLAGS := -I$(DEP_DIR)/xbyak_aarch64 -I$(DEP_DIR)/xbyak_aarch64/xbyak_aarch64 -w + +LIB_GROUPS := CORE CURLCOMPAT LRC IH264D ZARCHIVE IMGUI GLSLANG FMT PUGIXML ZLIB ZSTD +ifeq ($(HAVE_LIBUSB),1) + LIB_GROUPS += LIBUSB +endif +ifeq ($(ARCH_ARM64),1) + LIB_GROUPS += XBYAK +endif diff --git a/README.md b/README.md index f0cf27b6..19cbd8b4 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,48 @@ -# Cemu Libretro +# ParaLLEl GX2 -Cemu (Wii U emulator) as a libretro core for RetroArch. +Cemu (Wii U emulator) as a libretro core for RetroArch, `parallel_gx2_libretro`. Version: **2.6-372-g29f2f1fa**, upstream Cemu as merged (`29f2f1fa`, 15 Oct 2026). ## Getting the core -Through RetroArch: **Online Updater → Core Downloader → Cemu**. The libretro buildbot builds the `libretro` branch nightly for Windows x64, Linux x64 and arm64, macOS x64 and arm64, and Android arm64-v8a and x86_64, so the downloadable core is always the current tree. The CI also builds iOS, tvOS and webOS. There are no releases here on purpose: one more place to download from is one more place to be out of date. +Through RetroArch: **Online Updater → Core Downloader → Nintendo - Wii U (ParaLLEl GX2)**. The libretro buildbot builds the `libretro` branch nightly for Windows x64, Linux x64 and arm64, macOS x64 and arm64, and Android arm64-v8a and x86_64, so the downloadable core is always the current tree. The CI also builds iOS, tvOS and webOS. There are no releases here on purpose: one more place to download from is one more place to be out of date. Build it yourself for anything else, or to test a change. ## Build +A static Makefile builds the core and everything it uses: the libraries are +submodules built with it, and the network layer is libretro-common's (HTTP +and TLS, with no libcurl, OpenSSL or mbedTLS), so nothing but a C++20 +compiler and, on Linux, the system's GL and X11 headers is needed. + ```bash -# Install dependencies (Ubuntu/Debian) -sudo apt install -y cmake gcc g++ ninja-build nasm libpulse-dev \ - libsecret-1-dev libgcrypt20-dev libsystemd-dev freeglut3-dev +# Ubuntu/Debian: the headers the OpenGL and Vulkan surfaces are declared with +sudo apt install -y g++ make libgl-dev libegl-dev libx11-dev libxrender-dev libx11-xcb-dev -# Clone with submodules - vcpkg and the community graphic packs live in them, -# and configure fails with "Could not find toolchain file -# .../dependencies/vcpkg/..." without them. On an existing clone: -# git submodule update --init --recursive -git clone --recursive https://github.com/WizzardSK/cemu-libretro.git -cd cemu-libretro +# Clone with submodules - the libraries and the community graphic packs live +# in them. On an existing clone: git submodule update --init --recursive +git clone --recursive https://github.com/libretro/parallel-gx2.git +cd parallel-gx2 -# Configure (vcpkg handles dependencies automatically) -cmake -S . -B build -DCMAKE_BUILD_TYPE=release \ - -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ \ - -G Ninja - -# On arm64, vcpkg needs VCPKG_FORCE_SYSTEM_BINARIES=1, and -# VCPKG_MAX_CONCURRENCY is worth capping on low-memory boards. - -# Build -cmake --build build --target cemu_libretro - -# Result: bin/cemu_libretro.so +make -j$(nproc) +# Result: parallel_gx2_libretro.so ``` +The same Makefile builds the other targets: `make` under MSYS2 MinGW64 (or +with `CC`/`CXX` set to a MinGW-w64 cross compiler) for Windows, +`platform=osx` with `arch=arm64` or `arch=x86_64` for macOS, +`platform=ios-arm64` and `platform=tvos-arm64` (Metal only), +`platform=android-arm64` and `platform=android-x86_64` with `NDK_ROOT` set, +and a webOS SDK compiler as `CC`/`CXX` for webOS. `DEBUG=1` builds without +optimisation, `SANITIZER=address` with a sanitizer. + ## Install ```bash -cp bin/cemu_libretro.so ~/.config/retroarch/cores/ -cp cemu_libretro.info ~/.config/retroarch/info/ +cp parallel_gx2_libretro.so ~/.config/retroarch/cores/ +cp parallel_gx2_libretro.info ~/.config/retroarch/info/ ``` ## Setup @@ -117,17 +117,23 @@ git fetch cemuup main git merge cemuup/main ``` -Last sync: upstream `29f2f1fa` (merged 23 Oct 2026). The core reports its version as `git describe --tags --match v2.6 cemuup/main` (`2.6-372-g29f2f1fa`), set in `retro_get_system_info` and `cemu_libretro.info`; update both, and the version at the top of this README, on every sync. +Last sync: upstream `29f2f1fa` (merged 23 Oct 2026). The core reports its version as `git describe --tags --match v2.6 cemuup/main` (`2.6-372-g29f2f1fa`), set in `retro_get_system_info` and `parallel_gx2_libretro.info`; update both, and the version at the top of this README, on every sync. The fork has no `main` of its own: upstream is merged straight into `libretro`, the only long-lived branch here. What tends to break on a sync, none of which the conflict resolution shows: -- **Precompiled headers.** Upstream force-includes `precompiled.h` per target - via `cemu_use_precompiled_header()`. Targets this fork adds (`cemu_libretro`, - `CemuHeadlessGui`) have to call it too, or they lose `uint32`, `std::span` - and the `_mm_pause` shim. +- **Source lists.** The core builds from the Makefile, not upstream's CMake + (which `.upstream-excluded` removes on every merge): a source file upstream + adds or removes has to be added to or removed from `Makefile.common` by + hand. Every unit of the core is compiled with `precompiled.h` force-included, + as upstream's `cemu_use_precompiled_header()` does. +- **Boost and the network libraries.** The core has no Boost: the parts the + emulator uses are small headers in `dependencies/boost-compat`, and + `dependencies/curl-compat` carries libcurl's API over libretro-common's + HTTP and TLS. Upstream code that reaches for another Boost header, or a + libcurl or OpenSSL call these do not have yet, fails to build until they do. - **Copied functions drifting.** The libretro `VulkanRenderer` constructor is a copy of upstream's adapted for the shared device; upstream keeps changing the original underneath it. @@ -140,5 +146,5 @@ What tends to break on a sync, none of which the conflict resolution shows: Debugging a crash on aarch64: `backtrace()` cannot unwind past the signal frame, so the posix handler also records `fault/pc/lr/sp` into `/Cemu/log.txt`. -Convert `lr` with the module base from that log's `cemu_libretro.so(+0xOFFSET) -[0xABSOLUTE]` line and run `addr2line -Cfie bin/cemu_libretro.so `. +Convert `lr` with the module base from that log's `parallel_gx2_libretro.so(+0xOFFSET) +[0xABSOLUTE]` line and run `addr2line -Cfie parallel_gx2_libretro.so `. diff --git a/cmake/ECMFindModuleHelpers.cmake b/cmake/ECMFindModuleHelpers.cmake deleted file mode 100644 index a4837403..00000000 --- a/cmake/ECMFindModuleHelpers.cmake +++ /dev/null @@ -1,279 +0,0 @@ -# SPDX-FileCopyrightText: 2014 Alex Merry -# -# SPDX-License-Identifier: BSD-3-Clause - -#[=======================================================================[.rst: -ECMFindModuleHelpers --------------------- - -Helper macros for find modules: ``ecm_find_package_version_check()``, -``ecm_find_package_parse_components()`` and -``ecm_find_package_handle_library_components()``. - -:: - - ecm_find_package_version_check() - -Prints warnings if the CMake version or the project's required CMake version -is older than that required by extra-cmake-modules. - -:: - - ecm_find_package_parse_components( - RESULT_VAR - KNOWN_COMPONENTS [ [...]] - [SKIP_DEPENDENCY_HANDLING]) - -This macro will populate with a list of components found in -_FIND_COMPONENTS, after checking that all those components are in the -list of ``KNOWN_COMPONENTS``; if there are any unknown components, it will print -an error or warning (depending on the value of _FIND_REQUIRED) and call -``return()``. - -The order of components in is guaranteed to match the order they -are listed in the ``KNOWN_COMPONENTS`` argument. - -If ``SKIP_DEPENDENCY_HANDLING`` is not set, for each component the variable -__component_deps will be checked for dependent components. -If is listed in _FIND_COMPONENTS, then all its (transitive) -dependencies will also be added to . - -:: - - ecm_find_package_handle_library_components( - COMPONENTS [ [...]] - [SKIP_DEPENDENCY_HANDLING]) - [SKIP_PKG_CONFIG]) - -Creates an imported library target for each component. The operation of this -macro depends on the presence of a number of CMake variables. - -The __lib variable should contain the name of this library, -and __header variable should contain the name of a header -file associated with it (whatever relative path is normally passed to -'#include'). __header_subdir variable can be used to specify -which subdirectory of the include path the headers will be found in. -``ecm_find_package_components()`` will then search for the library -and include directory (creating appropriate cache variables) and create an -imported library target named ::. - -Additional variables can be used to provide additional information: - -If ``SKIP_PKG_CONFIG``, the __pkg_config variable is set, and -pkg-config is found, the pkg-config module given by -__pkg_config will be searched for and used to help locate the -library and header file. It will also be used to set -__VERSION. - -Note that if version information is found via pkg-config, -__FIND_VERSION can be set to require a particular version -for each component. - -If ``SKIP_DEPENDENCY_HANDLING`` is not set, the ``INTERFACE_LINK_LIBRARIES`` property -of the imported target for will be set to contain the imported -targets for the components listed in __component_deps. -_FOUND will also be set to ``FALSE`` if any of the components in -__component_deps are not found. This requires the components -in __component_deps to be listed before in the -``COMPONENTS`` argument. - -The following variables will be set: - -``_TARGETS`` - the imported targets -``_LIBRARIES`` - the found libraries -``_INCLUDE_DIRS`` - the combined required include directories for the components -``_DEFINITIONS`` - the "other" CFLAGS provided by pkg-config, if any -``_VERSION`` - the value of ``__VERSION`` for the first component that - has this variable set (note that components are searched for in the order - they are passed to the macro), although if it is already set, it will not - be altered - -.. note:: - These variables are never cleared, so if - ``ecm_find_package_handle_library_components()`` is called multiple times with - different components (typically because of multiple ``find_package()`` calls) then - ``_TARGETS``, for example, will contain all the targets found in any - call (although no duplicates). - -Since pre-1.0.0. -#]=======================================================================] - -include(CMakeParseArguments) - -macro(ecm_find_package_version_check module_name) - if(CMAKE_VERSION VERSION_LESS 3.16.0) - message(FATAL_ERROR "CMake 3.16.0 is required by Find${module_name}.cmake") - endif() - if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.16.0) - message(AUTHOR_WARNING "Your project should require at least CMake 3.16.0 to use Find${module_name}.cmake") - endif() -endmacro() - -macro(ecm_find_package_parse_components module_name) - set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING) - set(ecm_fppc_oneValueArgs RESULT_VAR) - set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS) - cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN}) - - if(ECM_FPPC_UNPARSED_ARGUMENTS) - message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}") - endif() - if(NOT ECM_FPPC_RESULT_VAR) - message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components") - endif() - if(NOT ECM_FPPC_KNOWN_COMPONENTS) - message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components") - endif() - if(NOT ECM_FPPC_DEFAULT_COMPONENTS) - set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS}) - endif() - - if(${module_name}_FIND_COMPONENTS) - set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS}) - - if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING) - # Make sure deps are included - foreach(ecm_fppc_comp ${ecm_fppc_requestedComps}) - foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps}) - list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index) - if("${ecm_fppc_index}" STREQUAL "-1") - if(NOT ${module_name}_FIND_QUIETLY) - message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}") - endif() - list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}") - endif() - endforeach() - endforeach() - else() - message(STATUS "Skipping dependency handling for ${module_name}") - endif() - list(REMOVE_DUPLICATES ecm_fppc_requestedComps) - - # This makes sure components are listed in the same order as - # KNOWN_COMPONENTS (potentially important for inter-dependencies) - set(${ECM_FPPC_RESULT_VAR}) - foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS}) - list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index) - if(NOT "${ecm_fppc_index}" STREQUAL "-1") - list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}") - list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index}) - endif() - endforeach() - # if there are any left, they are unknown components - if(ecm_fppc_requestedComps) - set(ecm_fppc_msgType STATUS) - if(${module_name}_FIND_REQUIRED) - set(ecm_fppc_msgType FATAL_ERROR) - endif() - if(NOT ${module_name}_FIND_QUIETLY) - message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}") - endif() - return() - endif() - else() - set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS}) - endif() -endmacro() - -macro(ecm_find_package_handle_library_components module_name) - set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING) - set(ecm_fpwc_oneValueArgs) - set(ecm_fpwc_multiValueArgs COMPONENTS) - cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN}) - - if(ECM_FPWC_UNPARSED_ARGUMENTS) - message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}") - endif() - if(NOT ECM_FPWC_COMPONENTS) - message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components") - endif() - - include(FindPackageHandleStandardArgs) - find_package(PkgConfig QUIET) - foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS}) - set(ecm_fpwc_dep_vars) - set(ecm_fpwc_dep_targets) - if(NOT SKIP_DEPENDENCY_HANDLING) - foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps}) - list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND") - list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}") - endforeach() - endif() - - if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config) - pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET - ${${module_name}_${ecm_fpwc_comp}_pkg_config}) - endif() - - find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR - NAMES ${${module_name}_${ecm_fpwc_comp}_header} - HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS} - PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir} - ) - find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY - NAMES ${${module_name}_${ecm_fpwc_comp}_lib} - HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS} - ) - - set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}") - if(NOT ${module_name}_VERSION) - set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION}) - endif() - - set(FPHSA_NAME_MISMATCHED 1) - find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp} - FOUND_VAR - ${module_name}_${ecm_fpwc_comp}_FOUND - REQUIRED_VARS - ${module_name}_${ecm_fpwc_comp}_LIBRARY - ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR - ${ecm_fpwc_dep_vars} - VERSION_VAR - ${module_name}_${ecm_fpwc_comp}_VERSION - ) - unset(FPHSA_NAME_MISMATCHED) - - mark_as_advanced( - ${module_name}_${ecm_fpwc_comp}_LIBRARY - ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR - ) - - if(${module_name}_${ecm_fpwc_comp}_FOUND) - list(APPEND ${module_name}_LIBRARIES - "${${module_name}_${ecm_fpwc_comp}_LIBRARY}") - list(APPEND ${module_name}_INCLUDE_DIRS - "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}") - set(${module_name}_DEFINITIONS - ${${module_name}_DEFINITIONS} - ${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}) - if(NOT TARGET ${module_name}::${ecm_fpwc_comp}) - add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED) - set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES - IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}" - INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}" - INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}" - ) - endif() - list(APPEND ${module_name}_TARGETS - "${module_name}::${ecm_fpwc_comp}") - endif() - endforeach() - if(${module_name}_LIBRARIES) - list(REMOVE_DUPLICATES ${module_name}_LIBRARIES) - endif() - if(${module_name}_INCLUDE_DIRS) - list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS) - endif() - if(${module_name}_DEFINITIONS) - list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS) - endif() - if(${module_name}_TARGETS) - list(REMOVE_DUPLICATES ${module_name}_TARGETS) - endif() -endmacro() diff --git a/cmake/ECMFindModuleHelpersStub.cmake b/cmake/ECMFindModuleHelpersStub.cmake deleted file mode 100644 index acc8c804..00000000 --- a/cmake/ECMFindModuleHelpersStub.cmake +++ /dev/null @@ -1 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpers.cmake) diff --git a/cmake/FindGTK3.cmake b/cmake/FindGTK3.cmake deleted file mode 100644 index 06f6c18e..00000000 --- a/cmake/FindGTK3.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -include(FindPackageHandleStandardArgs) - -find_package(PkgConfig) -if (PKG_CONFIG_FOUND) - pkg_search_module(GTK3 IMPORTED_TARGET gtk+-3.0) - if (GTK3_FOUND) - add_library(GTK3::gtk ALIAS PkgConfig::GTK3) - endif() - find_package_handle_standard_args(GTK3 - REQUIRED_VARS GTK3_LINK_LIBRARIES - VERSION_VAR GTK3_VERSION - ) -endif() diff --git a/cmake/FindWayland.cmake b/cmake/FindWayland.cmake deleted file mode 100644 index 926fd485..00000000 --- a/cmake/FindWayland.cmake +++ /dev/null @@ -1,137 +0,0 @@ -# SPDX-FileCopyrightText: 2014 Alex Merry -# SPDX-FileCopyrightText: 2014 Martin Gräßlin -# -# SPDX-License-Identifier: BSD-3-Clause - -#[=======================================================================[.rst: -FindWayland ------------ - -Try to find Wayland. - -This is a component-based find module, which makes use of the COMPONENTS -and OPTIONAL_COMPONENTS arguments to find_module. The following components -are available:: - - Client Server Cursor Egl - -If no components are specified, this module will act as though all components -were passed to OPTIONAL_COMPONENTS. - -This module will define the following variables, independently of the -components searched for or found: - -``Wayland_FOUND`` - TRUE if (the requested version of) Wayland is available -``Wayland_VERSION`` - Found Wayland version -``Wayland_TARGETS`` - A list of all targets imported by this module (note that there may be more - than the components that were requested) -``Wayland_LIBRARIES`` - This can be passed to target_link_libraries() instead of the imported - targets -``Wayland_INCLUDE_DIRS`` - This should be passed to target_include_directories() if the targets are - not used for linking -``Wayland_DEFINITIONS`` - This should be passed to target_compile_options() if the targets are not - used for linking -``Wayland_DATADIR`` - The core wayland protocols data directory - Since 5.73.0 - -For each searched-for components, ``Wayland__FOUND`` will be set to -TRUE if the corresponding Wayland library was found, and FALSE otherwise. If -``Wayland__FOUND`` is TRUE, the imported target -``Wayland::`` will be defined. This module will also attempt to -determine ``Wayland_*_VERSION`` variables for each imported target, although -``Wayland_VERSION`` should normally be sufficient. - -In general we recommend using the imported targets, as they are easier to use -and provide more control. Bear in mind, however, that if any target is in the -link interface of an exported library, it must be made available by the -package config file. - -Since pre-1.0.0. -#]=======================================================================] - -include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) - -ecm_find_package_version_check(Wayland) - -set(Wayland_known_components - Client - Server - Cursor - Egl -) -foreach(_comp ${Wayland_known_components}) - string(TOLOWER "${_comp}" _lc_comp) - set(Wayland_${_comp}_component_deps) - set(Wayland_${_comp}_pkg_config "wayland-${_lc_comp}") - set(Wayland_${_comp}_lib "wayland-${_lc_comp}") - set(Wayland_${_comp}_header "wayland-${_lc_comp}.h") -endforeach() -set(Wayland_Egl_component_deps Client) - -ecm_find_package_parse_components(Wayland - RESULT_VAR Wayland_components - KNOWN_COMPONENTS ${Wayland_known_components} -) -ecm_find_package_handle_library_components(Wayland - COMPONENTS ${Wayland_components} -) - -# If pkg-config didn't provide us with version information, -# try to extract it from wayland-version.h -# (Note that the version from wayland-egl.pc will probably be -# the Mesa version, rather than the Wayland version, but that -# version will be ignored as we always find wayland-client.pc -# first). -if(NOT Wayland_VERSION) - find_file(Wayland_VERSION_HEADER - NAMES wayland-version.h - HINTS ${Wayland_INCLUDE_DIRS} - ) - mark_as_advanced(Wayland_VERSION_HEADER) - if(Wayland_VERSION_HEADER) - file(READ ${Wayland_VERSION_HEADER} _wayland_version_header_contents) - string(REGEX REPLACE - "^.*[ \t]+WAYLAND_VERSION[ \t]+\"([0-9.]*)\".*$" - "\\1" - Wayland_VERSION - "${_wayland_version_header_contents}" - ) - unset(_wayland_version_header_contents) - endif() -endif() - -find_package_handle_standard_args(Wayland - FOUND_VAR - Wayland_FOUND - REQUIRED_VARS - Wayland_LIBRARIES - VERSION_VAR - Wayland_VERSION - HANDLE_COMPONENTS -) - -pkg_get_variable(Wayland_DATADIR wayland-scanner pkgdatadir) -if (CMAKE_CROSSCOMPILING AND (NOT EXISTS "${Wayland_DATADIR}/wayland.xml")) - # PKG_CONFIG_SYSROOT_DIR only applies to -I and -L flags, so pkg-config - # does not prepend CMAKE_SYSROOT when cross-compiling unless you pass - # --define-prefix explicitly. Therefore we have to manually do prepend - # it here when cross-compiling. - # See https://gitlab.kitware.com/cmake/cmake/-/issues/16647#note_844761 - set(Wayland_DATADIR ${CMAKE_SYSROOT}${Wayland_DATADIR}) -endif() -if (NOT EXISTS "${Wayland_DATADIR}/wayland.xml") - message(WARNING "Could not find wayland.xml in ${Wayland_DATADIR}") -endif() - -include(FeatureSummary) -set_package_properties(Wayland PROPERTIES - URL "https://wayland.freedesktop.org/" - DESCRIPTION "C library implementation of the Wayland protocol: a protocol for a compositor to talk to its clients" -) diff --git a/cmake/FindWaylandProtocols.cmake b/cmake/FindWaylandProtocols.cmake deleted file mode 100644 index a4449885..00000000 --- a/cmake/FindWaylandProtocols.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Vlad Zahorodnii -# -# SPDX-License-Identifier: BSD-3-Clause - -#[=======================================================================[.rst: -FindWaylandProtocols --------------------- - -Try to find wayland-protocols on a Unix system. - -This will define the following variables: - -``WaylandProtocols_FOUND`` - True if (the requested version of) wayland-protocols is available -``WaylandProtocols_VERSION`` - The version of wayland-protocols -``WaylandProtocols_DATADIR`` - The wayland protocols data directory -#]=======================================================================] - -find_package(PkgConfig QUIET) -pkg_check_modules(PKG_wayland_protocols QUIET wayland-protocols) - -set(WaylandProtocols_VERSION ${PKG_wayland_protocols_VERSION}) -pkg_get_variable(WaylandProtocols_DATADIR wayland-protocols pkgdatadir) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WaylandProtocols - FOUND_VAR WaylandProtocols_FOUND - REQUIRED_VARS WaylandProtocols_DATADIR - VERSION_VAR WaylandProtocols_VERSION -) - -include(FeatureSummary) -set_package_properties(WaylandProtocols PROPERTIES - DESCRIPTION "Specifications of extended Wayland protocols" - URL "https://wayland.freedesktop.org/" -) diff --git a/cmake/FindWaylandScanner.cmake b/cmake/FindWaylandScanner.cmake deleted file mode 100644 index f8495800..00000000 --- a/cmake/FindWaylandScanner.cmake +++ /dev/null @@ -1,162 +0,0 @@ -# SPDX-FileCopyrightText: 2012-2014 Pier Luigi Fiorini -# -# SPDX-License-Identifier: BSD-3-Clause - -#[=======================================================================[.rst: -FindWaylandScanner ------------------- - -Try to find wayland-scanner. - -If the wayland-scanner executable is not in your PATH, you can provide -an alternative name or full path location with the ``WaylandScanner_EXECUTABLE`` -variable. - -This will define the following variables: - -``WaylandScanner_FOUND`` - True if wayland-scanner is available. - -``WaylandScanner_EXECUTABLE`` - The wayland-scanner executable. - -If ``WaylandScanner_FOUND`` is TRUE, it will also define the following imported -target: - -``Wayland::Scanner`` - The wayland-scanner executable. - -This module provides the following functions to generate C protocol -implementations: - - - ``ecm_add_wayland_client_protocol`` - - ``ecm_add_wayland_server_protocol`` - -:: - - ecm_add_wayland_client_protocol( - PROTOCOL - BASENAME ) - - ecm_add_wayland_client_protocol( - PROTOCOL - BASENAME ) - -Generate Wayland client protocol files from ```` XML -definition for the ```` interface and append those files -to ```` or ````. - -:: - - ecm_add_wayland_server_protocol( - PROTOCOL - BASENAME ) - - ecm_add_wayland_server_protocol( - PROTOCOL - BASENAME ) - -Generate Wayland server protocol files from ```` XML -definition for the ```` interface and append those files -to ```` or ````. - -Since 1.4.0. -#]=======================================================================] - -include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) - -ecm_find_package_version_check(WaylandScanner) - -# Find wayland-scanner -find_program(WaylandScanner_EXECUTABLE NAMES wayland-scanner) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WaylandScanner - FOUND_VAR - WaylandScanner_FOUND - REQUIRED_VARS - WaylandScanner_EXECUTABLE -) - -mark_as_advanced(WaylandScanner_EXECUTABLE) - -if(NOT TARGET Wayland::Scanner AND WaylandScanner_FOUND) - add_executable(Wayland::Scanner IMPORTED) - set_target_properties(Wayland::Scanner PROPERTIES - IMPORTED_LOCATION "${WaylandScanner_EXECUTABLE}" - ) -endif() - -include(FeatureSummary) -set_package_properties(WaylandScanner PROPERTIES - URL "https://wayland.freedesktop.org/" - DESCRIPTION "Executable that converts XML protocol files to C code" -) - - -include(CMakeParseArguments) - -function(ecm_add_wayland_client_protocol target_or_sources_var) - # Parse arguments - set(oneValueArgs PROTOCOL BASENAME) - cmake_parse_arguments(ARGS "" "${oneValueArgs}" "" ${ARGN}) - - if(ARGS_UNPARSED_ARGUMENTS) - message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_client_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") - endif() - - get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) - set(_client_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-client-protocol.h") - set(_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") - - set_source_files_properties(${_client_header} GENERATED) - set_source_files_properties(${_code} GENERATED) - set_property(SOURCE ${_client_header} ${_code} PROPERTY SKIP_AUTOMOC ON) - - add_custom_command(OUTPUT "${_client_header}" - COMMAND ${WaylandScanner_EXECUTABLE} client-header ${_infile} ${_client_header} - DEPENDS ${_infile} VERBATIM) - - add_custom_command(OUTPUT "${_code}" - COMMAND ${WaylandScanner_EXECUTABLE} public-code ${_infile} ${_code} - DEPENDS ${_infile} ${_client_header} VERBATIM) - - if (TARGET ${target_or_sources_var}) - target_sources(${target_or_sources_var} PRIVATE "${_client_header}" "${_code}") - else() - list(APPEND ${target_or_sources_var} "${_client_header}" "${_code}") - set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) - endif() -endfunction() - - -function(ecm_add_wayland_server_protocol target_or_sources_var) - # Parse arguments - set(oneValueArgs PROTOCOL BASENAME) - cmake_parse_arguments(ARGS "" "${oneValueArgs}" "" ${ARGN}) - - if(ARGS_UNPARSED_ARGUMENTS) - message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_server_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") - endif() - - ecm_add_wayland_client_protocol(${target_or_sources_var} - PROTOCOL ${ARGS_PROTOCOL} - BASENAME ${ARGS_BASENAME}) - - get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) - set(_server_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-server-protocol.h") - set(_server_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") - set_property(SOURCE ${_server_header} ${_server_code} PROPERTY SKIP_AUTOMOC ON) - set_source_files_properties(${_server_header} GENERATED) - - add_custom_command(OUTPUT "${_server_header}" - COMMAND ${WaylandScanner_EXECUTABLE} server-header ${_infile} ${_server_header} - DEPENDS ${_infile} VERBATIM) - - if (TARGET ${target_or_sources_var}) - target_sources(${target_or_sources_var} PRIVATE "${_server_header}") - else() - list(APPEND ${target_or_sources_var} "${_server_header}") - set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) - endif() -endfunction() diff --git a/cmake/FindZArchive.cmake b/cmake/FindZArchive.cmake deleted file mode 100644 index 0c4b7714..00000000 --- a/cmake/FindZArchive.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -find_package(PkgConfig) - -if (PKG_CONFIG_FOUND) - pkg_search_module(zarchive IMPORTED_TARGET GLOBAL zarchive) - if (zarchive_FOUND) - add_library(ZArchive::zarchive ALIAS PkgConfig::zarchive) - endif() -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ZArchive - REQUIRED_VARS - zarchive_LINK_LIBRARIES - zarchive_FOUND - VERSION_VAR - zarchive_VERSION -) diff --git a/cmake/Findbluez.cmake b/cmake/Findbluez.cmake deleted file mode 100644 index 007cdac9..00000000 --- a/cmake/Findbluez.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -find_package(bluez CONFIG) -if (NOT bluez_FOUND) - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez) - if (bluez_FOUND) - add_library(bluez::bluez ALIAS PkgConfig::bluez) - endif () - endif () -endif () - -find_package_handle_standard_args(bluez - REQUIRED_VARS - bluez_LINK_LIBRARIES - bluez_FOUND - VERSION_VAR bluez_VERSION -) diff --git a/cmake/Findimgui.cmake b/cmake/Findimgui.cmake deleted file mode 100644 index 269297a1..00000000 --- a/cmake/Findimgui.cmake +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -include(FindPackageHandleStandardArgs) - -find_package(imgui CONFIG) -if (imgui_FOUND) - # Use upstream imguiConfig.cmake if possible - find_package_handle_standard_args(imgui CONFIG_MODE) -else() - # Fallback to pkg-config otherwise - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_search_module(imgui IMPORTED_TARGET GLOBAL imgui) - if (imgui_FOUND) - add_library(imgui::imgui ALIAS PkgConfig::imgui) - endif() - endif() - - find_package_handle_standard_args(imgui - REQUIRED_VARS - imgui_LINK_LIBRARIES - imgui_FOUND - VERSION_VAR - imgui_VERSION - ) -endif() diff --git a/cmake/Findlibusb.cmake b/cmake/Findlibusb.cmake deleted file mode 100644 index 85da6736..00000000 --- a/cmake/Findlibusb.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -find_package(libusb CONFIG) -if (NOT libusb_FOUND) - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_search_module(libusb IMPORTED_TARGET GLOBAL libusb-1.0 libusb) - if (libusb_FOUND) - add_library(libusb::libusb ALIAS PkgConfig::libusb) - endif () - endif () -endif () - -find_package_handle_standard_args(libusb - REQUIRED_VARS - libusb_LINK_LIBRARIES - libusb_FOUND - VERSION_VAR libusb_VERSION -) diff --git a/cmake/FindwxWidgets.cmake b/cmake/FindwxWidgets.cmake deleted file mode 100644 index 46abca0e..00000000 --- a/cmake/FindwxWidgets.cmake +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -include(FindPackageHandleStandardArgs) -find_package(wxWidgets CONFIG COMPONENTS ${wxWidgets_FIND_COMPONENTS}) - -if (wxWidgets_FOUND) - # Use upstream wxWidgetsConfig.cmake if possible - find_package_handle_standard_args(wxWidgets CONFIG_MODE) -else() - # Fall back to CMake's FindwxWidgets - # Temporarily unset CMAKE_MODULE_PATH to avoid calling the current find - # module recursively - set(_tmp_module_path "${CMAKE_MODULE_PATH}") - set(CMAKE_MODULE_PATH "") - - find_package(wxWidgets MODULE QUIET COMPONENTS ${wxWidgets_FIND_COMPONENTS}) - - set(CMAKE_MODULE_PATH "${_tmp_module_path}") - unset(_tmp_module_path) - - if (wxWidgets_FOUND) - add_library(wx::base IMPORTED INTERFACE) - target_include_directories(wx::base INTERFACE ${wxWidgets_INCLUDE_DIRS}) - target_link_libraries(wx::base INTERFACE ${wxWidgets_LIBRARIES}) - target_link_directories(wx::base INTERFACE ${wxWidgets_LIBRARY_DIRS}) - target_compile_definitions(wx::base INTERFACE ${wxWidgets_DEFINITIONS}) - target_compile_options(wx::base INTERFACE ${wxWidgets_CXX_FLAGS}) - - # FindwxWidgets sets everything into a single set of variables, so it is - # impossible to tell what libraries are required for what component. - # To be compatible with wxWidgetsConfig, we create an alias for each - # component so that the user can still use target_link_libraries(wx::gl) - foreach(component ${wxWidgets_FIND_COMPONENTS}) - if (NOT component STREQUAL "base") - # don't alias base to itself - add_library(wx::${component} ALIAS wx::base) - endif() - endforeach() - endif() - - find_package_handle_standard_args(wxWidgets - REQUIRED_VARS - wxWidgets_LIBRARIES - wxWidgets_FOUND - VERSION_VAR - wxWidgets_VERSION_STRING - ) -endif() diff --git a/cmake/Findzstd.cmake b/cmake/Findzstd.cmake deleted file mode 100644 index a8a45a67..00000000 --- a/cmake/Findzstd.cmake +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Andrea Pappacoda -# SPDX-License-Identifier: ISC - -include(FindPackageHandleStandardArgs) - -find_package(zstd CONFIG) -if (zstd_FOUND) - # Use upstream zstdConfig.cmake if possible - if (NOT TARGET zstd::zstd) - if (TARGET zstd::libzstd_static) - add_library(zstd::zstd ALIAS zstd::libzstd_static) - elseif (TARGET zstd::libzstd_shared) - add_library(zstd::zstd ALIAS zstd::libzstd_shared) - endif() - endif() - find_package_handle_standard_args(zstd CONFIG_MODE) -else() - # Fallback to pkg-config otherwise - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_search_module(libzstd IMPORTED_TARGET GLOBAL libzstd) - if (libzstd_FOUND) - add_library(zstd::zstd ALIAS PkgConfig::libzstd) - endif() - endif() - - find_package_handle_standard_args(zstd - REQUIRED_VARS - libzstd_LINK_LIBRARIES - libzstd_FOUND - VERSION_VAR libzstd_VERSION - ) -endif() diff --git a/dependencies/boost-compat/boost/algorithm/string.hpp b/dependencies/boost-compat/boost/algorithm/string.hpp new file mode 100644 index 00000000..ee46c458 --- /dev/null +++ b/dependencies/boost-compat/boost/algorithm/string.hpp @@ -0,0 +1,175 @@ +// The Boost.StringAlgo predicates and case helpers the emulator uses. Case is +// folded per character with the C library's tolower/towlower, which is what +// Boost does under the default ("C") locale. +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace algorithm { + +namespace detail { + +template +inline C fold(C c) +{ + if constexpr (sizeof(C) == 1) + return (C)std::tolower((unsigned char)c); + else + return (C)std::towlower((std::wint_t)c); +} + +template +inline C fold_upper(C c) +{ + if constexpr (sizeof(C) == 1) + return (C)std::toupper((unsigned char)c); + else + return (C)std::towupper((std::wint_t)c); +} + +template +struct char_of +{ + using type = std::remove_cv_t()))>>; +}; +template struct char_of { using type = std::remove_cv_t; }; +template struct char_of { using type = std::remove_cv_t; }; +template struct char_of { using type = std::remove_cv_t; }; + +// Every argument as a view: strings, views, string literals and char pointers. +template +inline auto view(const T& s) +{ + using C = typename char_of>::type; + if constexpr (std::is_pointer_v>) + return std::basic_string_view(s); + else + return std::basic_string_view(std::data(s), std::size(s)); +} + +// The two sides may differ in character type (a std::string_view against a +// wide literal); characters are compared by value, as Boost does. +template +inline bool equal_with(std::basic_string_view a, std::basic_string_view b, Eq eq) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), eq); +} + +template +inline bool find_with(std::basic_string_view haystack, std::basic_string_view needle, Eq eq) +{ + return std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), eq) != haystack.end(); +} + +struct is_equal { template bool operator()(A a, B b) const { return (wchar_t)a == (wchar_t)b; } }; +struct is_iequal { template bool operator()(A a, B b) const { return (wchar_t)fold(a) == (wchar_t)fold(b); } }; + +} // namespace detail + +template +inline bool equals(const A& a, const B& b) +{ + return detail::equal_with(detail::view(a), detail::view(b), detail::is_equal()); +} + +template +inline bool iequals(const A& a, const B& b) +{ + return detail::equal_with(detail::view(a), detail::view(b), detail::is_iequal()); +} + +template +inline bool starts_with(const A& s, const B& prefix) +{ + auto v = detail::view(s); + auto p = detail::view(prefix); + return v.size() >= p.size() && detail::equal_with(v.substr(0, p.size()), p, detail::is_equal()); +} + +template +inline bool istarts_with(const A& s, const B& prefix) +{ + auto v = detail::view(s); + auto p = detail::view(prefix); + return v.size() >= p.size() && detail::equal_with(v.substr(0, p.size()), p, detail::is_iequal()); +} + +template +inline bool ends_with(const A& s, const B& suffix) +{ + auto v = detail::view(s); + auto p = detail::view(suffix); + return v.size() >= p.size() && detail::equal_with(v.substr(v.size() - p.size()), p, detail::is_equal()); +} + +template +inline bool iends_with(const A& s, const B& suffix) +{ + auto v = detail::view(s); + auto p = detail::view(suffix); + return v.size() >= p.size() && detail::equal_with(v.substr(v.size() - p.size()), p, detail::is_iequal()); +} + +template +inline bool contains(const A& s, const B& needle) +{ + return detail::find_with(detail::view(s), detail::view(needle), detail::is_equal()); +} + +template +inline bool icontains(const A& s, const B& needle) +{ + return detail::find_with(detail::view(s), detail::view(needle), detail::is_iequal()); +} + +template +inline void to_lower(S& s) +{ + for (auto& c : s) + c = detail::fold(c); +} + +template +inline void to_upper(S& s) +{ + for (auto& c : s) + c = detail::fold_upper(c); +} + +template +inline S to_lower_copy(S s) +{ + to_lower(s); + return s; +} + +template +inline S to_upper_copy(S s) +{ + to_upper(s); + return s; +} + +} // namespace algorithm + +using algorithm::contains; +using algorithm::ends_with; +using algorithm::equals; +using algorithm::icontains; +using algorithm::iends_with; +using algorithm::iequals; +using algorithm::istarts_with; +using algorithm::starts_with; +using algorithm::to_lower; +using algorithm::to_lower_copy; +using algorithm::to_upper; +using algorithm::to_upper_copy; + +} // namespace boost diff --git a/dependencies/boost-compat/boost/bind/placeholders.hpp b/dependencies/boost-compat/boost/bind/placeholders.hpp new file mode 100644 index 00000000..2f54473d --- /dev/null +++ b/dependencies/boost-compat/boost/bind/placeholders.hpp @@ -0,0 +1,21 @@ +// boost::bind and its placeholders: the standard library's. +#pragma once + +#include + +namespace boost { + +namespace placeholders { +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; +using std::placeholders::_4; +} // namespace placeholders + +template +inline auto bind(F&& f, Args&&... args) +{ + return std::bind(std::forward(f), std::forward(args)...); +} + +} // namespace boost diff --git a/dependencies/boost-compat/boost/container/detail/inline_vector.hpp b/dependencies/boost-compat/boost/container/detail/inline_vector.hpp new file mode 100644 index 00000000..cf9345ec --- /dev/null +++ b/dependencies/boost-compat/boost/container/detail/inline_vector.hpp @@ -0,0 +1,365 @@ +// The storage behind boost::container::small_vector and static_vector in this +// tree: up to N elements live inside the object, and a small_vector moves to +// the heap past that while a static_vector stops there (as Boost does, with an +// exception). Only the subset of the Boost interface the emulator uses, with +// the same semantics. +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace container { namespace detail { + +template +class inline_vector +{ +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + inline_vector() noexcept : m_data(inline_data()), m_size(0), m_capacity(N) {} + + explicit inline_vector(size_type count) : inline_vector() + { + resize(count); + } + + inline_vector(size_type count, const T& value) : inline_vector() + { + assign(count, value); + } + + template::iterator_category> + inline_vector(It first, It last) : inline_vector() + { + assign(first, last); + } + + inline_vector(std::initializer_list list) : inline_vector() + { + assign(list.begin(), list.end()); + } + + inline_vector(const inline_vector& other) : inline_vector() + { + assign(other.begin(), other.end()); + } + + inline_vector(inline_vector&& other) noexcept(std::is_nothrow_move_constructible_v) : inline_vector() + { + take(std::move(other)); + } + + ~inline_vector() + { + clear(); + release_heap(); + } + + inline_vector& operator=(const inline_vector& other) + { + if (this != &other) + assign(other.begin(), other.end()); + return *this; + } + + inline_vector& operator=(inline_vector&& other) noexcept(std::is_nothrow_move_constructible_v) + { + if (this != &other) + { + clear(); + take(std::move(other)); + } + return *this; + } + + inline_vector& operator=(std::initializer_list list) + { + assign(list.begin(), list.end()); + return *this; + } + + void assign(size_type count, const T& value) + { + clear(); + reserve(count); + for (size_type i = 0; i < count; i++) + ::new ((void*)(m_data + i)) T(value); + m_size = count; + } + + template::iterator_category> + void assign(It first, It last) + { + clear(); + if constexpr (std::is_base_of_v::iterator_category>) + reserve((size_type)std::distance(first, last)); + for (; first != last; ++first) + emplace_back(*first); + } + + void assign(std::initializer_list list) { assign(list.begin(), list.end()); } + + reference at(size_type pos) + { + if (pos >= m_size) + throw std::out_of_range("inline_vector::at"); + return m_data[pos]; + } + + const_reference at(size_type pos) const + { + if (pos >= m_size) + throw std::out_of_range("inline_vector::at"); + return m_data[pos]; + } + + reference operator[](size_type pos) noexcept { return m_data[pos]; } + const_reference operator[](size_type pos) const noexcept { return m_data[pos]; } + reference front() noexcept { return m_data[0]; } + const_reference front() const noexcept { return m_data[0]; } + reference back() noexcept { return m_data[m_size - 1]; } + const_reference back() const noexcept { return m_data[m_size - 1]; } + T* data() noexcept { return m_data; } + const T* data() const noexcept { return m_data; } + + iterator begin() noexcept { return m_data; } + const_iterator begin() const noexcept { return m_data; } + const_iterator cbegin() const noexcept { return m_data; } + iterator end() noexcept { return m_data + m_size; } + const_iterator end() const noexcept { return m_data + m_size; } + const_iterator cend() const noexcept { return m_data + m_size; } + reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } + reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } + + bool empty() const noexcept { return m_size == 0; } + size_type size() const noexcept { return m_size; } + size_type capacity() const noexcept { return m_capacity; } + size_type max_size() const noexcept { return CanGrow ? (size_type)-1 / sizeof(T) : N; } + static constexpr size_type static_capacity = N; + + void reserve(size_type count) + { + if (count > m_capacity) + grow_to(count); + } + + void shrink_to_fit() noexcept {} + + void clear() noexcept + { + std::destroy(m_data, m_data + m_size); + m_size = 0; + } + + template + reference emplace_back(Args&&... args) + { + if (m_size == m_capacity) + grow_to(next_capacity(m_size + 1)); + T* p = ::new ((void*)(m_data + m_size)) T(std::forward(args)...); + m_size++; + return *p; + } + + void push_back(const T& value) { emplace_back(value); } + void push_back(T&& value) { emplace_back(std::move(value)); } + + void pop_back() noexcept + { + m_size--; + std::destroy_at(m_data + m_size); + } + + template + iterator emplace(const_iterator pos, Args&&... args) + { + const size_type index = (size_type)(pos - m_data); + if (index == m_size) + { + emplace_back(std::forward(args)...); + return m_data + index; + } + // built before the move, as the argument may refer into this vector + T value(std::forward(args)...); + emplace_back(std::move(m_data[m_size - 1])); + std::move_backward(m_data + index, m_data + m_size - 2, m_data + m_size - 1); + m_data[index] = std::move(value); + return m_data + index; + } + + iterator insert(const_iterator pos, const T& value) { return emplace(pos, value); } + iterator insert(const_iterator pos, T&& value) { return emplace(pos, std::move(value)); } + + iterator insert(const_iterator pos, size_type count, const T& value) + { + const size_type index = (size_type)(pos - m_data); + const T copy(value); + for (size_type i = 0; i < count; i++) + emplace(m_data + index + i, copy); + return m_data + index; + } + + template::iterator_category> + iterator insert(const_iterator pos, It first, It last) + { + const size_type index = (size_type)(pos - m_data); + const size_type oldSize = m_size; + for (; first != last; ++first) + emplace_back(*first); + std::rotate(m_data + index, m_data + oldSize, m_data + m_size); + return m_data + index; + } + + iterator insert(const_iterator pos, std::initializer_list list) + { + return insert(pos, list.begin(), list.end()); + } + + iterator erase(const_iterator pos) + { + return erase(pos, pos + 1); + } + + iterator erase(const_iterator first, const_iterator last) + { + T* f = m_data + (first - m_data); + T* l = m_data + (last - m_data); + if (f != l) + { + T* newEnd = std::move(l, m_data + m_size, f); + std::destroy(newEnd, m_data + m_size); + m_size = (size_type)(newEnd - m_data); + } + return f; + } + + void resize(size_type count) + { + if (count < m_size) + { + std::destroy(m_data + count, m_data + m_size); + m_size = count; + return; + } + reserve(count); + for (; m_size < count; m_size++) + ::new ((void*)(m_data + m_size)) T(); + } + + void resize(size_type count, const T& value) + { + if (count < m_size) + { + std::destroy(m_data + count, m_data + m_size); + m_size = count; + return; + } + reserve(count); + for (; m_size < count; m_size++) + ::new ((void*)(m_data + m_size)) T(value); + } + + void swap(inline_vector& other) + { + inline_vector tmp(std::move(other)); + other = std::move(*this); + *this = std::move(tmp); + } + + friend bool operator==(const inline_vector& a, const inline_vector& b) + { + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); + } + + friend bool operator!=(const inline_vector& a, const inline_vector& b) { return !(a == b); } + + friend bool operator<(const inline_vector& a, const inline_vector& b) + { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); + } + +private: + static constexpr std::size_t kInline = N ? N : 1; + + T* inline_data() noexcept { return std::launder(reinterpret_cast(m_inline)); } + bool is_inline() const noexcept { return m_data == reinterpret_cast(m_inline); } + + size_type next_capacity(size_type needed) const + { + size_type c = m_capacity ? m_capacity * 2 : 4; + return c < needed ? needed : c; + } + + void grow_to(size_type count) + { + if constexpr (!CanGrow) + { + throw std::bad_alloc(); + } + else + { + T* p = static_cast(::operator new(count * sizeof(T), std::align_val_t(alignof(T)))); + std::uninitialized_move(m_data, m_data + m_size, p); + std::destroy(m_data, m_data + m_size); + release_heap(); + m_data = p; + m_capacity = count; + } + } + + void release_heap() noexcept + { + if (!is_inline()) + ::operator delete(m_data, std::align_val_t(alignof(T))); + m_data = inline_data(); + m_capacity = N; + } + + // other is left empty; this is empty on entry + void take(inline_vector&& other) + { + if (!other.is_inline()) + { + release_heap(); + m_data = other.m_data; + m_size = other.m_size; + m_capacity = other.m_capacity; + other.m_data = other.inline_data(); + other.m_size = 0; + other.m_capacity = N; + return; + } + reserve(other.m_size); + std::uninitialized_move(other.m_data, other.m_data + other.m_size, m_data); + m_size = other.m_size; + other.clear(); + } + + T* m_data; + size_type m_size; + size_type m_capacity; + alignas(T) unsigned char m_inline[kInline * sizeof(T)]; +}; + +}}} // namespace boost::container::detail diff --git a/dependencies/boost-compat/boost/container/small_vector.hpp b/dependencies/boost-compat/boost/container/small_vector.hpp new file mode 100644 index 00000000..c85e9ffa --- /dev/null +++ b/dependencies/boost-compat/boost/container/small_vector.hpp @@ -0,0 +1,26 @@ +// boost::container::small_vector, on the storage in detail/inline_vector.hpp. +#pragma once + +#include "detail/inline_vector.hpp" + +namespace boost { namespace container { + +template +class small_vector : public detail::inline_vector +{ + using base = detail::inline_vector; +public: + using base::base; + small_vector() = default; + small_vector(const small_vector&) = default; + small_vector(small_vector&&) = default; + small_vector& operator=(const small_vector&) = default; + small_vector& operator=(small_vector&&) = default; + small_vector& operator=(std::initializer_list list) + { + base::assign(list.begin(), list.end()); + return *this; + } +}; + +}} // namespace boost::container diff --git a/dependencies/boost-compat/boost/container/static_vector.hpp b/dependencies/boost-compat/boost/container/static_vector.hpp new file mode 100644 index 00000000..901daf64 --- /dev/null +++ b/dependencies/boost-compat/boost/container/static_vector.hpp @@ -0,0 +1,27 @@ +// boost::container::static_vector, on the storage in detail/inline_vector.hpp: +// never more than Capacity elements, and no heap. +#pragma once + +#include "detail/inline_vector.hpp" + +namespace boost { namespace container { + +template +class static_vector : public detail::inline_vector +{ + using base = detail::inline_vector; +public: + using base::base; + static_vector() = default; + static_vector(const static_vector&) = default; + static_vector(static_vector&&) = default; + static_vector& operator=(const static_vector&) = default; + static_vector& operator=(static_vector&&) = default; + static_vector& operator=(std::initializer_list list) + { + base::assign(list.begin(), list.end()); + return *this; + } +}; + +}} // namespace boost::container diff --git a/dependencies/boost-compat/boost/core/demangle.hpp b/dependencies/boost-compat/boost/core/demangle.hpp new file mode 100644 index 00000000..fa13135d --- /dev/null +++ b/dependencies/boost-compat/boost/core/demangle.hpp @@ -0,0 +1,29 @@ +// boost::core::demangle: the C++ ABI's demangler where there is one, the name +// as it is elsewhere. +#pragma once + +#include +#include + +#if defined(__GNUC__) || defined(__clang__) +#include +#endif + +namespace boost { namespace core { + +inline std::string demangle(const char* name) +{ +#if defined(__GNUC__) || defined(__clang__) + int status = 0; + char* d = abi::__cxa_demangle(name, nullptr, nullptr, &status); + if (d) + { + std::string out(d); + std::free(d); + return out; + } +#endif + return name; +} + +}} // namespace boost::core diff --git a/dependencies/boost-compat/boost/dynamic_bitset.hpp b/dependencies/boost-compat/boost/dynamic_bitset.hpp new file mode 100644 index 00000000..d1bd97a6 --- /dev/null +++ b/dependencies/boost-compat/boost/dynamic_bitset.hpp @@ -0,0 +1,112 @@ +// boost::dynamic_bitset: a bit set sized at run time, with the operations the +// recompiler uses. Bits past size() are kept clear. +#pragma once + +#include +#include + +namespace boost { + +template +class dynamic_bitset +{ + static constexpr std::size_t kBits = sizeof(Block) * 8; +public: + using size_type = std::size_t; + + dynamic_bitset() = default; + explicit dynamic_bitset(size_type n, unsigned long value = 0) { resize(n); if (n && value) m_blocks[0] = (Block)value; trim(); } + + size_type size() const noexcept { return m_size; } + bool empty() const noexcept { return m_size == 0; } + + void resize(size_type n, bool value = false) + { + const size_type old = m_size; + m_blocks.resize((n + kBits - 1) / kBits, value ? ~(Block)0 : (Block)0); + m_size = n; + if (value) + for (size_type i = old; i < n && i < ((old + kBits - 1) / kBits) * kBits; i++) + set(i); + trim(); + } + + bool test(size_type i) const { return (m_blocks[i / kBits] >> (i % kBits)) & 1; } + bool operator[](size_type i) const { return test(i); } + + dynamic_bitset& set(size_type i, bool value = true) + { + if (value) + m_blocks[i / kBits] |= (Block)1 << (i % kBits); + else + m_blocks[i / kBits] &= ~((Block)1 << (i % kBits)); + return *this; + } + + dynamic_bitset& set() + { + for (auto& b : m_blocks) + b = ~(Block)0; + trim(); + return *this; + } + + dynamic_bitset& reset(size_type i) { return set(i, false); } + + dynamic_bitset& reset() + { + for (auto& b : m_blocks) + b = 0; + return *this; + } + + bool any() const + { + for (auto b : m_blocks) + if (b) + return true; + return false; + } + + bool none() const { return !any(); } + + dynamic_bitset& operator&=(const dynamic_bitset& o) + { + for (size_type i = 0; i < m_blocks.size() && i < o.m_blocks.size(); i++) + m_blocks[i] &= o.m_blocks[i]; + for (size_type i = o.m_blocks.size(); i < m_blocks.size(); i++) + m_blocks[i] = 0; + return *this; + } + + dynamic_bitset& operator|=(const dynamic_bitset& o) + { + for (size_type i = 0; i < m_blocks.size() && i < o.m_blocks.size(); i++) + m_blocks[i] |= o.m_blocks[i]; + return *this; + } + + bool operator==(const dynamic_bitset& o) const { return m_size == o.m_size && m_blocks == o.m_blocks; } + bool operator!=(const dynamic_bitset& o) const { return !(*this == o); } + + size_type count() const + { + size_type n = 0; + for (auto b : m_blocks) + for (; b; b &= b - 1) + n++; + return n; + } + +private: + void trim() + { + if (m_size % kBits && !m_blocks.empty()) + m_blocks.back() &= ((Block)1 << (m_size % kBits)) - 1; + } + + std::vector m_blocks; + size_type m_size = 0; +}; + +} // namespace boost diff --git a/dependencies/boost-compat/boost/functional/hash_fwd.hpp b/dependencies/boost-compat/boost/functional/hash_fwd.hpp new file mode 100644 index 00000000..831dd3ff --- /dev/null +++ b/dependencies/boost-compat/boost/functional/hash_fwd.hpp @@ -0,0 +1,15 @@ +// boost::hash_combine, with Boost's mixing. +#pragma once + +#include +#include + +namespace boost { + +template +inline void hash_combine(std::size_t& seed, const T& v) +{ + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +} // namespace boost diff --git a/dependencies/boost-compat/boost/iterator/transform_iterator.hpp b/dependencies/boost-compat/boost/iterator/transform_iterator.hpp new file mode 100644 index 00000000..13cd6859 --- /dev/null +++ b/dependencies/boost-compat/boost/iterator/transform_iterator.hpp @@ -0,0 +1,41 @@ +// boost::make_transform_iterator: an iterator that hands out f(*it). +#pragma once + +#include +#include +#include + +namespace boost { + +template +class transform_iterator +{ +public: + using iterator_category = std::forward_iterator_tag; + using reference = decltype(std::declval()(*std::declval())); + using value_type = std::remove_cv_t>; + using difference_type = typename std::iterator_traits::difference_type; + using pointer = void; + + transform_iterator() = default; + transform_iterator(It it, F f) : m_it(std::move(it)), m_f(std::move(f)) {} + + reference operator*() const { return m_f(*m_it); } + transform_iterator& operator++() { ++m_it; return *this; } + transform_iterator operator++(int) { transform_iterator t = *this; ++m_it; return t; } + bool operator==(const transform_iterator& o) const { return m_it == o.m_it; } + bool operator!=(const transform_iterator& o) const { return m_it != o.m_it; } + const It& base() const { return m_it; } + +private: + It m_it{}; + F m_f{}; +}; + +template +inline transform_iterator make_transform_iterator(It it, F f) +{ + return transform_iterator(std::move(it), std::move(f)); +} + +} // namespace boost diff --git a/dependencies/boost-compat/boost/nowide/convert.hpp b/dependencies/boost-compat/boost/nowide/convert.hpp new file mode 100644 index 00000000..f109770b --- /dev/null +++ b/dependencies/boost-compat/boost/nowide/convert.hpp @@ -0,0 +1,154 @@ +// boost::nowide::narrow and widen: UTF-8 to and from wchar_t text (UTF-16 +// where wchar_t is 16 bits, UTF-32 elsewhere). As in Boost.Nowide, a malformed +// sequence becomes U+FFFD rather than an error. +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace nowide { + +namespace detail { + +constexpr char32_t kReplacement = 0xFFFD; + +// Decodes one code point at s[i], moving i past it. +inline char32_t decode_utf8(const char* s, std::size_t n, std::size_t& i) +{ + const unsigned char c = (unsigned char)s[i++]; + if (c < 0x80) + return c; + int extra; + char32_t cp; + char32_t min; + if ((c & 0xE0) == 0xC0) { extra = 1; cp = c & 0x1F; min = 0x80; } + else if ((c & 0xF0) == 0xE0) { extra = 2; cp = c & 0x0F; min = 0x800; } + else if ((c & 0xF8) == 0xF0) { extra = 3; cp = c & 0x07; min = 0x10000; } + else return kReplacement; + for (int k = 0; k < extra; k++) + { + if (i >= n || ((unsigned char)s[i] & 0xC0) != 0x80) + return kReplacement; + cp = (cp << 6) | ((unsigned char)s[i++] & 0x3F); + } + if (cp < min || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) + return kReplacement; + return cp; +} + +inline void encode_utf8(std::string& out, char32_t cp) +{ + if (cp < 0x80) + out.push_back((char)cp); + else if (cp < 0x800) + { + out.push_back((char)(0xC0 | (cp >> 6))); + out.push_back((char)(0x80 | (cp & 0x3F))); + } + else if (cp < 0x10000) + { + out.push_back((char)(0xE0 | (cp >> 12))); + out.push_back((char)(0x80 | ((cp >> 6) & 0x3F))); + out.push_back((char)(0x80 | (cp & 0x3F))); + } + else + { + out.push_back((char)(0xF0 | (cp >> 18))); + out.push_back((char)(0x80 | ((cp >> 12) & 0x3F))); + out.push_back((char)(0x80 | ((cp >> 6) & 0x3F))); + out.push_back((char)(0x80 | (cp & 0x3F))); + } +} + +inline std::wstring widen(const char* s, std::size_t n) +{ + std::wstring out; + out.reserve(n); + std::size_t i = 0; + while (i < n) + { + const char32_t cp = decode_utf8(s, n, i); + if constexpr (sizeof(wchar_t) == 2) + { + if (cp >= 0x10000) + { + const char32_t v = cp - 0x10000; + out.push_back((wchar_t)(0xD800 + (v >> 10))); + out.push_back((wchar_t)(0xDC00 + (v & 0x3FF))); + continue; + } + } + out.push_back((wchar_t)cp); + } + return out; +} + +inline std::string narrow(const wchar_t* s, std::size_t n) +{ + std::string out; + out.reserve(n); + for (std::size_t i = 0; i < n; i++) + { + char32_t cp = (char32_t)(std::make_unsigned_t)s[i]; + if constexpr (sizeof(wchar_t) == 2) + { + if (cp >= 0xD800 && cp <= 0xDBFF) + { + if (i + 1 < n && (char32_t)s[i + 1] >= 0xDC00 && (char32_t)s[i + 1] <= 0xDFFF) + { + cp = 0x10000 + ((cp - 0xD800) << 10) + ((char32_t)s[i + 1] - 0xDC00); + i++; + } + else + cp = kReplacement; + } + else if (cp >= 0xDC00 && cp <= 0xDFFF) + cp = kReplacement; + } + else if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) + cp = kReplacement; + encode_utf8(out, cp); + } + return out; +} + +} // namespace detail + +inline std::wstring widen(const char* s) { return detail::widen(s, std::strlen(s)); } +inline std::wstring widen(const char* s, std::size_t n) { return detail::widen(s, n); } +inline std::wstring widen(const char* begin, const char* end) { return detail::widen(begin, (std::size_t)(end - begin)); } +inline std::wstring widen(std::string_view s) { return detail::widen(s.data(), s.size()); } +inline std::wstring widen(const std::string& s) { return detail::widen(s.data(), s.size()); } + +// Into a buffer the caller owns, terminator included: the buffer, or null if +// it is too small. +inline wchar_t* widen(wchar_t* output, std::size_t output_size, const char* source) +{ + const std::wstring w = widen(source); + if (w.size() + 1 > output_size) + return nullptr; + std::memcpy(output, w.c_str(), (w.size() + 1) * sizeof(wchar_t)); + return output; +} + +inline std::string narrow(const wchar_t* s) { return detail::narrow(s, std::wcslen(s)); } +inline std::string narrow(const wchar_t* s, std::size_t n) { return detail::narrow(s, n); } +inline std::string narrow(const wchar_t* begin, const wchar_t* end) { return detail::narrow(begin, (std::size_t)(end - begin)); } +inline std::string narrow(std::wstring_view s) { return detail::narrow(s.data(), s.size()); } +inline std::string narrow(const std::wstring& s) { return detail::narrow(s.data(), s.size()); } + +inline char* narrow(char* output, std::size_t output_size, const wchar_t* source) +{ + const std::string n = narrow(source); + if (n.size() + 1 > output_size) + return nullptr; + std::memcpy(output, n.c_str(), n.size() + 1); + return output; +} + +}} // namespace boost::nowide diff --git a/dependencies/boost-compat/boost/predef.h b/dependencies/boost-compat/boost/predef.h new file mode 100644 index 00000000..0513320d --- /dev/null +++ b/dependencies/boost-compat/boost/predef.h @@ -0,0 +1,4 @@ +// Only the operating system tests of Boost.Predef are used here. +#pragma once + +#include "predef/os.h" diff --git a/dependencies/boost-compat/boost/predef/os.h b/dependencies/boost-compat/boost/predef/os.h new file mode 100644 index 00000000..9af945cf --- /dev/null +++ b/dependencies/boost-compat/boost/predef/os.h @@ -0,0 +1,43 @@ +// The BOOST_OS_* tests of Boost.Predef the emulator uses, detected the way +// Boost.Predef does (same macros, same order, so an earlier match hides a +// later one: iOS before macOS, Linux before Unix-only). +#pragma once + +#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) +#define BOOST_OS_IOS 1 +#else +#define BOOST_OS_IOS 0 +#endif + +#if !BOOST_OS_IOS && (defined(linux) || defined(__linux) || defined(__linux__) || defined(__gnu_linux__)) +#define BOOST_OS_LINUX 1 +#else +#define BOOST_OS_LINUX 0 +#endif + +#if !BOOST_OS_IOS && !BOOST_OS_LINUX && (defined(macintosh) || defined(Macintosh) || (defined(__APPLE__) && defined(__MACH__))) +#define BOOST_OS_MACOS 1 +#else +#define BOOST_OS_MACOS 0 +#endif + +#if !BOOST_OS_IOS && !BOOST_OS_LINUX && !BOOST_OS_MACOS && \ + (defined(BSD) || defined(_SYSTYPE_BSD) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__bsdi__)) +#define BOOST_OS_BSD 1 +#else +#define BOOST_OS_BSD 0 +#endif + +#if defined(unix) || defined(__unix) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE) +#define BOOST_OS_UNIX 1 +#else +#define BOOST_OS_UNIX 0 +#endif + +#if !BOOST_OS_IOS && !BOOST_OS_LINUX && !BOOST_OS_MACOS && !BOOST_OS_BSD && \ + (defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__)) +#define BOOST_OS_WINDOWS 1 +#else +#define BOOST_OS_WINDOWS 0 +#endif diff --git a/dependencies/boost-compat/boost/property_tree/ini_parser.hpp b/dependencies/boost-compat/boost/property_tree/ini_parser.hpp new file mode 100644 index 00000000..e83a814c --- /dev/null +++ b/dependencies/boost-compat/boost/property_tree/ini_parser.hpp @@ -0,0 +1,131 @@ +// boost::property_tree::ptree and read_ini, for flat INI files: a key is +// "Section.name" (or "name" before the first section). get throws +// when the key is missing and get_optional returns nothing, as in Boost. +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace property_tree { + +class ptree_bad_path : public std::runtime_error +{ +public: + explicit ptree_bad_path(const std::string& path) : std::runtime_error("No such node (" + path + ")") {} +}; + +namespace ini_parser { +class ini_parser_error : public std::runtime_error +{ +public: + ini_parser_error(const std::string& message, unsigned long line) + : std::runtime_error("(" + std::to_string(line) + "): " + message) {} +}; +} // namespace ini_parser + +class ptree +{ +public: + template + T get(const std::string& path) const + { + auto it = m_values.find(path); + if (it == m_values.end()) + throw ptree_bad_path(path); + return convert(it->second, path); + } + + template + T get(const std::string& path, const T& fallback) const + { + auto it = m_values.find(path); + if (it == m_values.end()) + return fallback; + return convert(it->second, path); + } + + template + std::optional get_optional(const std::string& path) const + { + auto it = m_values.find(path); + if (it == m_values.end()) + return std::nullopt; + return convert(it->second, path); + } + + void put(const std::string& path, const std::string& value) { m_values[path] = value; } + + bool empty() const { return m_values.empty(); } + +private: + template + static T convert(const std::string& s, const std::string& path) + { + if constexpr (std::is_same_v) + return s; + else + { + std::istringstream in(s); + T v{}; + in >> v; + if (in.fail()) + throw ptree_bad_path(path); + return v; + } + } + + std::map m_values; +}; + +namespace ini_parser { + +inline void read_ini(std::istream& in, ptree& pt) +{ + auto trim = [](std::string s) { + const char* ws = " \t\r\n"; + const auto b = s.find_first_not_of(ws); + if (b == std::string::npos) + return std::string(); + const auto e = s.find_last_not_of(ws); + return s.substr(b, e - b + 1); + }; + ptree result; + std::string section; + std::string line; + unsigned long lineNo = 0; + while (std::getline(in, line)) + { + lineNo++; + line = trim(line); + if (line.empty() || line[0] == ';' || line[0] == '#') + continue; + if (line[0] == '[') + { + const auto end = line.find(']'); + if (end == std::string::npos) + throw ini_parser_error("unmatched '['", lineNo); + section = trim(line.substr(1, end - 1)); + continue; + } + const auto eq = line.find('='); + if (eq == std::string::npos) + throw ini_parser_error("'=' character not found in line", lineNo); + const std::string key = trim(line.substr(0, eq)); + if (key.empty()) + throw ini_parser_error("key expected", lineNo); + result.put(section.empty() ? key : section + "." + key, trim(line.substr(eq + 1))); + } + pt = std::move(result); +} + +} // namespace ini_parser + +using ini_parser::read_ini; + +}} // namespace boost::property_tree diff --git a/dependencies/boost-compat/boost/random/uniform_int.hpp b/dependencies/boost-compat/boost/random/uniform_int.hpp new file mode 100644 index 00000000..5e3d6124 --- /dev/null +++ b/dependencies/boost-compat/boost/random/uniform_int.hpp @@ -0,0 +1,11 @@ +// boost::random::uniform_int_distribution: the standard library's. +#pragma once + +#include + +namespace boost { namespace random { + +template +using uniform_int_distribution = std::uniform_int_distribution; + +}} // namespace boost::random diff --git a/dependencies/boost-compat/boost/range/adaptor/reversed.hpp b/dependencies/boost-compat/boost/range/adaptor/reversed.hpp new file mode 100644 index 00000000..1ef3c689 --- /dev/null +++ b/dependencies/boost-compat/boost/range/adaptor/reversed.hpp @@ -0,0 +1,30 @@ +// range | boost::adaptors::reversed: the range walked back to front. +#pragma once + +#include + +namespace boost { namespace adaptors { + +namespace detail { + +struct reversed_tag {}; + +template +struct reversed_range +{ + R& r; + auto begin() const { return std::rbegin(r); } + auto end() const { return std::rend(r); } +}; + +template +inline reversed_range operator|(R& r, reversed_tag) { return reversed_range{r}; } + +template +inline reversed_range operator|(const R& r, reversed_tag) { return reversed_range{r}; } + +} // namespace detail + +inline constexpr detail::reversed_tag reversed{}; + +}} // namespace boost::adaptors diff --git a/dependencies/boost-compat/boost/signals2.hpp b/dependencies/boost-compat/boost/signals2.hpp new file mode 100644 index 00000000..299bc82f --- /dev/null +++ b/dependencies/boost-compat/boost/signals2.hpp @@ -0,0 +1,120 @@ +// boost::signals2::signal and connection, reduced to what EventService uses: +// connect, disconnect and emission, from any thread. Slots run in the order +// they were connected, as in Boost; a slot may disconnect itself while it runs. +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace boost { namespace signals2 { + +namespace detail { + +struct slot_list_base +{ + virtual ~slot_list_base() = default; + virtual void remove(uint64_t id) = 0; +}; + +} // namespace detail + +class connection +{ +public: + connection() = default; + connection(std::weak_ptr list, uint64_t id) : m_list(std::move(list)), m_id(id) {} + + void disconnect() const + { + if (auto list = m_list.lock()) + list->remove(m_id); + } + + bool connected() const { return !m_list.expired() && m_id != 0; } + + uint64_t id() const { return m_id; } + +private: + std::weak_ptr m_list; + uint64_t m_id = 0; +}; + +template +class signal; + +template +class signal +{ + struct slot_list : detail::slot_list_base + { + std::mutex mutex; + std::vector>>> slots; + uint64_t nextId = 1; + + void remove(uint64_t id) override + { + std::lock_guard lock(mutex); + for (auto it = slots.begin(); it != slots.end(); ++it) + { + if (it->first == id) + { + slots.erase(it); + return; + } + } + } + }; + +public: + signal() : m_list(std::make_shared()) {} + signal(const signal&) = delete; + signal& operator=(const signal&) = delete; + + template + connection connect(F&& f) + { + std::lock_guard lock(m_list->mutex); + const uint64_t id = m_list->nextId++; + m_list->slots.emplace_back(id, std::make_shared>(std::forward(f))); + return connection(m_list, id); + } + + void disconnect(const connection& c) { m_list->remove(c.id()); } + + void disconnect_all_slots() + { + std::lock_guard lock(m_list->mutex); + m_list->slots.clear(); + } + + bool empty() const + { + std::lock_guard lock(m_list->mutex); + return m_list->slots.empty(); + } + + // The slots are called outside the lock, on a snapshot, so one may connect + // or disconnect without deadlocking. + template + void operator()(CallArgs&&... args) + { + std::vector>> snapshot; + { + std::lock_guard lock(m_list->mutex); + snapshot.reserve(m_list->slots.size()); + for (auto& s : m_list->slots) + snapshot.push_back(s.second); + } + for (auto& s : snapshot) + (*s)(args...); + } + +private: + std::shared_ptr m_list; +}; + +}} // namespace boost::signals2 diff --git a/dependencies/boost-compat/boost/static_string/static_string.hpp b/dependencies/boost-compat/boost/static_string/static_string.hpp new file mode 100644 index 00000000..1d297b6f --- /dev/null +++ b/dependencies/boost-compat/boost/static_string/static_string.hpp @@ -0,0 +1,82 @@ +// boost::static_string: a string of at most N characters stored inline. +#pragma once + +#include +#include +#include +#include + +namespace boost { + +template +class basic_static_string +{ +public: + using size_type = std::size_t; + + basic_static_string() noexcept { m_data[0] = 0; } + basic_static_string(const CharT* s) { assign(s); } + + size_type size() const noexcept { return m_size; } + size_type length() const noexcept { return m_size; } + static constexpr size_type capacity() noexcept { return N; } + static constexpr size_type max_size() noexcept { return N; } + bool empty() const noexcept { return m_size == 0; } + const CharT* c_str() const noexcept { return m_data; } + const CharT* data() const noexcept { return m_data; } + CharT* data() noexcept { return m_data; } + CharT& back() noexcept { return m_data[m_size - 1]; } + const CharT& back() const noexcept { return m_data[m_size - 1]; } + CharT& front() noexcept { return m_data[0]; } + CharT& operator[](size_type i) noexcept { return m_data[i]; } + const CharT& operator[](size_type i) const noexcept { return m_data[i]; } + const CharT* begin() const noexcept { return m_data; } + const CharT* end() const noexcept { return m_data + m_size; } + operator std::basic_string_view() const noexcept { return {m_data, m_size}; } + + void clear() noexcept { m_size = 0; m_data[0] = 0; } + + void push_back(CharT c) + { + if (m_size >= N) + throw std::length_error("static_string::push_back"); + m_data[m_size++] = c; + m_data[m_size] = 0; + } + + void pop_back() noexcept + { + m_data[--m_size] = 0; + } + + basic_static_string& assign(std::basic_string_view s) + { + if (s.size() > N) + throw std::length_error("static_string::assign"); + std::memcpy(m_data, s.data(), s.size() * sizeof(CharT)); + m_size = s.size(); + m_data[m_size] = 0; + return *this; + } + + basic_static_string& assign(const CharT* s) { return assign(std::basic_string_view(s)); } + + int compare(std::basic_string_view s) const noexcept + { + return std::basic_string_view(m_data, m_size).compare(s); + } + + int compare(const CharT* s) const noexcept { return compare(std::basic_string_view(s)); } + + friend bool operator==(const basic_static_string& a, std::basic_string_view b) { return a.compare(b) == 0; } + friend bool operator!=(const basic_static_string& a, std::basic_string_view b) { return a.compare(b) != 0; } + +private: + CharT m_data[N + 1]; + size_type m_size = 0; +}; + +template +using static_string = basic_static_string; + +} // namespace boost diff --git a/dependencies/curl-compat/curl_compat.cpp b/dependencies/curl-compat/curl_compat.cpp new file mode 100644 index 00000000..7a1854e4 --- /dev/null +++ b/dependencies/curl-compat/curl_compat.cpp @@ -0,0 +1,1347 @@ +/* libcurl's API over libretro-common's net_http (see curl/curl.h). + * + * net_http's DNS cache and connection pool belong to one thread, so + * every transfer runs on a single transfer thread. A caller hands it a + * transfer through a lock-free stack and takes the results - header + * lines, body runs, the outcome - from the transfer's own + * single-producer queue, running its callbacks on its own thread the + * way libcurl does. Nothing takes a lock: the queues are atomics, and + * a side with nothing to do sleeps on an eventcount. + * + * As in libcurl, curl_global_init() and curl_global_cleanup() are not + * thread-safe: the core calls them once, from retro_init() and + * retro_deinit(); curl_easy_init() calls curl_global_init() when no + * one has. */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* net_socket_ssl_retro.c reports refused certificates through these */ +extern "C" void ssl_socket_log_verify_fail(int mode_required, + const char *domain, const char *info) +{ + (void)mode_required; (void)domain; (void)info; +} +extern "C" void ssl_socket_log_verify_disabled(const char *domain) +{ + (void)domain; +} + +struct curl_tls_settings +{ + std::vector> trust; + std::vector cert; + std::vector keyDer; + struct rsa_private_key key; + std::vector suites; + bool hasKey = false; + bool verifyPeer = true; + bool verifyHost = true; + /* what net_http is given, pointing into the above */ + std::vector trustPtr; + std::vector trustLen; + const uint8_t *chainPtr = nullptr; + size_t chainLen = 0; + struct ssl_socket_retro_config cfg; + + const struct ssl_socket_retro_config *build() + { + memset(&cfg, 0, sizeof(cfg)); + trustPtr.clear(); + trustLen.clear(); + for (auto &t : trust) + { + trustPtr.push_back(t.data()); + trustLen.push_back(t.size()); + } + if (!trust.empty()) + { + cfg.trust = trustPtr.data(); + cfg.trust_lens = trustLen.data(); + cfg.trust_count = (unsigned)trust.size(); + } + if (hasKey) + { + chainPtr = cert.data(); + chainLen = cert.size(); + cfg.chain = &chainPtr; + cfg.chain_lens = &chainLen; + cfg.chain_count = 1; + cfg.key = &key; + } + if (!suites.empty()) + { + cfg.suites = suites.data(); + cfg.suite_count = (unsigned)suites.size(); + } + if (!verifyPeer) + cfg.flags |= SSL_RETRO_NO_VERIFY; + if (!verifyHost) + cfg.flags |= SSL_RETRO_NO_HOSTNAME; + return &cfg; + } +}; + +bool curl_tls_add_trust(struct curl_tls_settings *tls, const void *der, size_t len) +{ + if (!tls || !der || !len) + return false; + tls->trust.emplace_back((const uint8_t*)der, (const uint8_t*)der + len); + return true; +} + +bool curl_tls_set_client(struct curl_tls_settings *tls, + const void *cert_der, size_t cert_len, const void *key_der, size_t key_len) +{ + if (!tls || !cert_der || !cert_len || !key_der || !key_len) + return false; + tls->cert.assign((const uint8_t*)cert_der, (const uint8_t*)cert_der + cert_len); + tls->keyDer.assign((const uint8_t*)key_der, (const uint8_t*)key_der + key_len); + /* the parsed key points into keyDer */ + tls->hasKey = x509_rsa_private_key_parse(&tls->key, tls->keyDer.data(), tls->keyDer.size()) == 0; + return tls->hasKey; +} + +void curl_tls_set_suites(struct curl_tls_settings *tls, const unsigned *suites, size_t count) +{ + if (tls) + tls->suites.assign(suites, suites + count); +} + +void curl_tls_set_verify(struct curl_tls_settings *tls, bool peer, bool host) +{ + if (!tls) + return; + tls->verifyPeer = peer; + tls->verifyHost = host; +} + +namespace +{ + enum class EventKind { Header, Body, Done }; + + struct Event + { + std::atomic next{nullptr}; + EventKind kind = EventKind::Body; + std::string data; + CURLcode result = CURLE_OK; + long status = 0; + }; + + /* The transfer thread stops reading a transfer whose caller has + * this much unread. */ + constexpr size_t kMaxQueued = 1u << 20; + + void wakeTransferThread(); + + /* One request: built by the caller, run by the transfer thread, + * whose results go back through the event queue. Two owners: the + * last of the two to let go frees it. */ + struct Transfer + { + std::atomic refs{2}; + Transfer *nextSubmit = nullptr; + /* request */ + std::string url; + std::string method; + std::string headers; + std::string contentType; + std::string userAgent; + std::vector body; + bool hasBody = false; + bool hasTls = false; + curl_tls_settings tls; + std::chrono::steady_clock::time_point deadline{}; + bool hasDeadline = false; + /* caller -> transfer thread */ + std::atomic abandoned{false}; + std::atomic recvPaused{false}; + /* transfer thread -> caller: a single-producer queue whose head + * the caller owns and whose tail the transfer thread owns */ + Event *head; + Event *tail; + std::atomic posted{0}; + std::atomic queuedBytes{0}; + retro_eventcount_t ec; /* the caller sleeps here */ + /* transfer thread only */ + struct http_t *http = nullptr; + bool headersSent = false; + bool finished = false; + + Transfer() + { + head = tail = new Event(); /* stub */ + retro_eventcount_init(&ec); + } + + ~Transfer() + { + retro_eventcount_free(&ec); + while (head) + { + Event *n = head->next.load(std::memory_order_relaxed); + delete head; + head = n; + } + } + + void post(Event *e) + { + if (e->kind == EventKind::Body) + queuedBytes.fetch_add(e->data.size(), std::memory_order_relaxed); + tail->next.store(e, std::memory_order_release); + tail = e; + posted.fetch_add(1, std::memory_order_release); + retro_eventcount_notify(&ec); + } + + /* the caller's side: the next event, or null */ + Event *peek() + { + return head->next.load(std::memory_order_acquire); + } + + void pop() + { + Event *n = head->next.load(std::memory_order_acquire); + if (n->kind == EventKind::Body) + { + const size_t was = queuedBytes.fetch_sub(n->data.size(), std::memory_order_relaxed); + if (was >= kMaxQueued && was - n->data.size() < kMaxQueued) + wakeTransferThread(); /* it stopped reading for this one */ + } + delete head; + head = n; /* n becomes the stub */ + } + + void release() + { + if (refs.fetch_sub(1, std::memory_order_acq_rel) == 1) + delete this; + } + }; + + std::atomic g_submitted{nullptr}; + retro_eventcount_t g_wake; /* the transfer thread sleeps here */ + std::atomic g_stopping{false}; + bool g_running = false; + std::thread g_thread; + + void wakeTransferThread() + { + retro_eventcount_notify(&g_wake); + } + + CURLcode failureCode(struct http_t *h) + { + int code = 0; + const char *stage = net_http_failure(h, &code); + if (!stage) + return CURLE_RECV_ERROR; + if (strstr(stage, "dns")) + return CURLE_COULDNT_RESOLVE_HOST; + if (strstr(stage, "ssl_config")) + return CURLE_SSL_CERTPROBLEM; + if (strstr(stage, "ssl")) + return CURLE_SSL_CONNECT_ERROR; + if (strstr(stage, "connect") || strstr(stage, "socket_create")) + return CURLE_COULDNT_CONNECT; + if (strstr(stage, "send") || strstr(stage, "source")) + return CURLE_SEND_ERROR; + if (strstr(stage, "redirect")) + return CURLE_TOO_MANY_REDIRECTS; + return CURLE_RECV_ERROR; + } + + /* The status line and header lines, as libcurl hands them to a + * header callback: each with its CRLF, then an empty one. */ + void postHeaders(Transfer *t) + { + if (t->headersSent) + return; + t->headersSent = true; + const int status = net_http_status(t->http); + char *block = net_http_headers_take(t->http, true); + if (status <= 0 && !block) + return; + Event *e = new Event(); + e->kind = EventKind::Header; + e->status = status; + e->data = "HTTP/1.1 " + std::to_string(status) + "\r\n"; + for (const char *l = net_http_header_next(block, nullptr); l; l = net_http_header_next(block, l)) + { + e->data += l; + e->data += "\r\n"; + } + e->data += "\r\n"; + free(block); + t->post(e); + } + + bool sink(void *userdata, const void *data, size_t len) + { + Transfer *t = (Transfer*)userdata; + if (t->abandoned.load(std::memory_order_relaxed)) + return false; + postHeaders(t); + Event *e = new Event(); + e->kind = EventKind::Body; + e->data.assign((const char*)data, len); + t->post(e); + return true; + } + + void finish(Transfer *t, CURLcode result) + { + long status = 0; + if (t->http) + { + postHeaders(t); + status = net_http_status(t->http); + net_http_delete(t->http); + t->http = nullptr; + } + Event *e = new Event(); + e->kind = EventKind::Done; + e->result = result; + e->status = status; + t->post(e); + t->finished = true; + } + + bool start(Transfer *t) + { + struct http_connection_t *conn = net_http_connection_new(t->url.c_str(), t->method.c_str(), nullptr); + if (!conn) + return false; + if (!t->userAgent.empty()) + net_http_connection_set_user_agent(conn, t->userAgent.c_str()); + if (!t->headers.empty()) + net_http_connection_set_headers(conn, t->headers.c_str()); + if (t->hasBody) + net_http_connection_set_content(conn, t->contentType.empty() ? nullptr : t->contentType.c_str(), + t->body.size(), t->body.data()); + if (t->hasTls) + net_http_connection_set_tls(conn, t->tls.build()); + net_http_connection_set_sink(conn, sink, t); + if (!net_http_connection_iterate(conn) || !net_http_connection_done(conn) + || !(t->http = net_http_new(conn))) + { + net_http_connection_free(conn); + return false; + } + net_http_connection_free(conn); + return true; + } + + void transferThread() + { + std::vector active; + net_http_init(); + for (;;) + { + const int key = retro_eventcount_prepare_wait(&g_wake); + if (g_stopping.load(std::memory_order_acquire)) + { + retro_eventcount_cancel_wait(&g_wake); + break; + } + + /* take the new ones, oldest first */ + Transfer *list = g_submitted.exchange(nullptr, std::memory_order_acquire); + std::vector fresh; + for (; list; list = list->nextSubmit) + fresh.push_back(list); + for (auto it = fresh.rbegin(); it != fresh.rend(); ++it) + { + Transfer *t = *it; + if (t->abandoned.load(std::memory_order_relaxed)) + finish(t, CURLE_ABORTED_BY_CALLBACK); + else if (!start(t)) + finish(t, CURLE_URL_MALFORMAT); + if (t->finished) + t->release(); + else + active.push_back(t); + } + + bool moved = false; + const auto now = std::chrono::steady_clock::now(); + for (size_t i = 0; i < active.size();) + { + Transfer *t = active[i]; + if (t->abandoned.load(std::memory_order_relaxed)) + finish(t, CURLE_ABORTED_BY_CALLBACK); + else if (t->hasDeadline && now >= t->deadline) + finish(t, CURLE_OPERATION_TIMEDOUT); + else if (t->recvPaused.load(std::memory_order_relaxed) + || t->queuedBytes.load(std::memory_order_relaxed) >= kMaxQueued) + { + /* the caller is not reading: neither is this */ + } + else + { + size_t pos = 0, total = 0; + const uint32_t before = t->posted.load(std::memory_order_relaxed); + if (net_http_update(t->http, &pos, &total)) + { + const int status = net_http_status(t->http); + finish(t, status > 0 ? CURLE_OK : failureCode(t->http)); + } + if (t->posted.load(std::memory_order_relaxed) != before) + moved = true; + } + if (t->finished) + { + moved = true; + t->release(); + active[i] = active.back(); + active.pop_back(); + } + else + i++; + } + + if (moved) + { + retro_eventcount_cancel_wait(&g_wake); + continue; + } + if (active.empty()) + { + /* nothing to run: sleep until handed something */ + retro_eventcount_commit_wait(&g_wake, key); + continue; + } + /* Wait on the sockets. net_http_wait() takes one transfer; + * with more than one, the wait is short so that each gets + * its turn, and new work waits at most that long. */ + Transfer *first = nullptr; + for (Transfer *t : active) + if (!t->recvPaused.load(std::memory_order_relaxed) + && t->queuedBytes.load(std::memory_order_relaxed) < kMaxQueued) + { + first = t; + break; + } + if (!first) + retro_eventcount_commit_wait(&g_wake, key); + else + { + retro_eventcount_cancel_wait(&g_wake); + net_http_wait(first->http, active.size() == 1 ? 50 : 5); + } + } + for (Transfer *t : active) + { + finish(t, CURLE_ABORTED_BY_CALLBACK); + t->release(); + } + net_http_deinit(); + } + + void submit(Transfer *t) + { + Transfer *head = g_submitted.load(std::memory_order_relaxed); + do + t->nextSubmit = head; + while (!g_submitted.compare_exchange_weak(head, t, std::memory_order_release, std::memory_order_relaxed)); + wakeTransferThread(); + } + + struct Easy + { + std::string url; + std::string customRequest; + std::string userAgent; + std::vector headers; + const void *postFields = nullptr; + const struct curl_httppost *httpPost = nullptr; + curl_off_t postFieldSize = -1; + curl_off_t inFileSize = -1; + bool post = false; + bool noBody = false; + bool noProgress = true; + bool verifyPeer = true; + bool verifyHost = true; + long timeoutMs = 0; + curl_write_callback writeFn = nullptr; + void *writeData = nullptr; + curl_write_callback headerFn = nullptr; + void *headerData = nullptr; + curl_read_callback readFn = nullptr; + void *readData = nullptr; + curl_progress_callback progressFn = nullptr; + void *progressData = nullptr; + curl_ssl_ctx_callback sslCtxFn = nullptr; + void *sslCtxData = nullptr; + /* the request in flight */ + Transfer *xfer = nullptr; + bool inMulti = false; + bool done = false; + CURLcode result = CURLE_OK; + std::atomic pauseBits{0}; + size_t bodyOffset = 0; /* of the body run being delivered */ + /* what curl_easy_getinfo() reports */ + long status = 0; + double downloaded = 0; + double uploaded = 0; + double contentLength = -1; + double seconds = 0; + std::chrono::steady_clock::time_point started; + std::string contentType; + + void reset() + { + this->~Easy(); + new (this) Easy(); + } + + ~Easy() + { + abandon(); + } + + void abandon() + { + if (xfer) + { + xfer->abandoned.store(true, std::memory_order_relaxed); + wakeTransferThread(); + xfer->release(); + xfer = nullptr; + } + } + }; + + std::string headerValue(const std::string &h) + { + size_t i = h.find(':') + 1; + while (i < h.size() && (h[i] == ' ' || h[i] == '\t')) + i++; + return h.substr(i); + } + + bool equalsNoCase(const char *a, const char *b, size_t n) + { + for (size_t i = 0; i < n; i++) + { + char x = a[i], y = b[i]; + if (x >= 'A' && x <= 'Z') x += 'a' - 'A'; + if (y >= 'A' && y <= 'Z') y += 'a' - 'A'; + if (x != y) + return false; + } + return true; + } + + bool headerIs(const std::string &h, const char *name) + { + const size_t n = strlen(name); + return h.size() > n && equalsNoCase(h.c_str(), name, n) && h[n] == ':'; + } + + /* The value of header @name in a block of CRLF-ended lines. */ + bool findHeader(const std::string &block, const char *name, std::string &value) + { + size_t a = 0; + while (a < block.size()) + { + size_t b = block.find("\r\n", a); + if (b == std::string::npos) + b = block.size(); + const std::string line = block.substr(a, b - a); + if (headerIs(line, name)) + { + value = headerValue(line); + return true; + } + a = b + 2; + } + return false; + } + + /* Builds the transfer and hands it over. */ + CURLcode begin(CURL *handle, Easy *e) + { + e->abandon(); + e->done = false; + e->result = CURLE_OK; + e->status = 0; + e->downloaded = 0; + e->uploaded = 0; + e->contentLength = -1; + e->bodyOffset = 0; + e->contentType.clear(); + e->pauseBits.store(0, std::memory_order_relaxed); + e->started = std::chrono::steady_clock::now(); + if (e->url.empty()) + return CURLE_URL_MALFORMAT; + + Transfer *t = new Transfer(); + t->url = e->url; + if (!e->customRequest.empty()) + t->method = e->customRequest; + else if (e->noBody) + t->method = "HEAD"; + else if (e->post) + t->method = "POST"; + else + t->method = "GET"; + t->userAgent = e->userAgent; + /* net_http writes Host, Content-Length, Accept-Encoding and + * User-Agent itself; the caller's Content-Type goes in its own + * slot so net_http does not add a default one */ + for (const std::string &h : e->headers) + { + if (headerIs(h, "Host") || headerIs(h, "Content-Length") || headerIs(h, "Accept-Encoding") + || headerIs(h, "Expect") || headerIs(h, "Transfer-Encoding")) + continue; + if (headerIs(h, "User-Agent")) + { + t->userAgent = headerValue(h); + continue; + } + if (headerIs(h, "Content-Type")) + { + t->contentType = headerValue(h); + continue; + } + if (h.find(':') == std::string::npos) + continue; /* "Name;" sends an empty header in libcurl; dropped */ + t->headers += h; + t->headers += "\r\n"; + } + if (e->post) + { + t->hasBody = true; + if (e->httpPost && !e->postFields) + { + /* multipart/form-data: each part with its name, the boundary + * one no part can contain */ + std::string boundary = "------------------------curlcompat"; + std::string body; + for (;;) + { + bool clash = false; + for (const struct curl_httppost *f = e->httpPost; f && !clash; f = f->next) + clash = f->contents && std::string(f->contents, (size_t)f->contentslength).find(boundary) != std::string::npos; + if (!clash) + break; + boundary += "x"; + } + for (const struct curl_httppost *f = e->httpPost; f; f = f->next) + { + body += "--" + boundary + "\r\nContent-Disposition: form-data; name=\""; + body.append(f->name, (size_t)f->namelength); + body += "\"\r\n"; + if (f->contenttype) + body += std::string("Content-Type: ") + f->contenttype + "\r\n"; + body += "\r\n"; + if (f->contents) + body.append(f->contents, (size_t)f->contentslength); + body += "\r\n"; + } + body += "--" + boundary + "--\r\n"; + t->body.assign(body.begin(), body.end()); + if (t->contentType.empty()) + t->contentType = "multipart/form-data; boundary=" + boundary; + } + else if (e->postFields) + { + const size_t n = e->postFieldSize >= 0 ? (size_t)e->postFieldSize : strlen((const char*)e->postFields); + t->body.assign((const uint8_t*)e->postFields, (const uint8_t*)e->postFields + n); + } + else if (e->readFn) + { + /* the body, read whole */ + char buf[16384]; + for (;;) + { + size_t want = sizeof(buf); + if (e->inFileSize >= 0) + { + if ((curl_off_t)t->body.size() >= e->inFileSize) + break; + want = (size_t)std::min((curl_off_t)want, e->inFileSize - (curl_off_t)t->body.size()); + } + const size_t got = e->readFn(buf, 1, want, e->readData); + if (got == CURL_READFUNC_ABORT) + { + delete t; + return CURLE_ABORTED_BY_CALLBACK; + } + if (got == 0 || got > want) + break; + t->body.insert(t->body.end(), buf, buf + got); + } + } + e->uploaded = (double)t->body.size(); + } + const bool https = equalsNoCase(e->url.c_str(), "https:", e->url.size() >= 6 ? 6 : 0) && e->url.size() >= 6; + if (https && (e->sslCtxFn || !e->verifyPeer || !e->verifyHost)) + { + t->hasTls = true; + if (e->sslCtxFn) + { + const CURLcode r = e->sslCtxFn(handle, &t->tls, e->sslCtxData); + if (r != CURLE_OK) + { + delete t; + return r; + } + } + if (!e->verifyPeer) + t->tls.verifyPeer = false; + if (!e->verifyHost) + t->tls.verifyHost = false; + } + if (e->timeoutMs > 0) + { + t->hasDeadline = true; + t->deadline = e->started + std::chrono::milliseconds(e->timeoutMs); + } + e->xfer = t; + submit(t); + return CURLE_OK; + } + + /* Ends a transfer the caller gave up on; retire() then lets go. */ + void stop(Transfer *t) + { + t->abandoned.store(true, std::memory_order_relaxed); + wakeTransferThread(); + } + + /* Lets go of a transfer that is over. */ + void retire(Easy *e) + { + if (e->done && e->xfer) + { + e->xfer->release(); + e->xfer = nullptr; + } + } + + /* Hands what has arrived to the callbacks; e->done once the transfer + * is over, and then retire() lets go of it. */ + void deliver(Easy *e) + { + Transfer *t = e->xfer; + if (!t) + return; + for (Event *ev = t->peek(); ev; ev = t->peek()) + { + if (ev->kind == EventKind::Header) + { + e->status = ev->status; + if (e->headerFn) + { + size_t a = 0; + while (a < ev->data.size()) + { + size_t b = ev->data.find("\r\n", a); + b = b == std::string::npos ? ev->data.size() : b + 2; + std::string line = ev->data.substr(a, b - a); + if (headerIs(line, "Content-Type")) + { + e->contentType = headerValue(line); + while (!e->contentType.empty() && (e->contentType.back() == '\r' || e->contentType.back() == '\n')) + e->contentType.pop_back(); + } + if (headerIs(line, "Content-Length")) + e->contentLength = atof(headerValue(line).c_str()); + if (e->headerFn(&line[0], 1, line.size(), e->headerData) != line.size()) + { + e->result = CURLE_WRITE_ERROR; + e->done = true; + stop(t); + return; + } + a = b; + } + } + else + { + std::string v; + if (findHeader(ev->data, "Content-Type", v)) + e->contentType = v; + if (findHeader(ev->data, "Content-Length", v)) + e->contentLength = atof(v.c_str()); + } + t->pop(); + } + else if (ev->kind == EventKind::Body) + { + if (e->pauseBits.load(std::memory_order_relaxed) & CURLPAUSE_RECV) + return; + size_t n = ev->data.size() - e->bodyOffset; + if (e->writeFn) + { + const size_t r = e->writeFn(&ev->data[e->bodyOffset], 1, n, e->writeData); + if (r == CURL_WRITEFUNC_PAUSE) + { + /* this run again on resume */ + e->pauseBits.fetch_or(CURLPAUSE_RECV, std::memory_order_relaxed); + t->recvPaused.store(true, std::memory_order_relaxed); + return; + } + if (r != n) + { + e->result = CURLE_WRITE_ERROR; + e->done = true; + stop(t); + return; + } + } + e->downloaded += (double)n; + e->bodyOffset = 0; + t->pop(); + if (e->progressFn && !e->noProgress + && e->progressFn(e->progressData, e->contentLength > 0 ? e->contentLength : 0, + e->downloaded, e->uploaded, e->uploaded) != 0) + { + e->result = CURLE_ABORTED_BY_CALLBACK; + e->done = true; + stop(t); + return; + } + } + else + { + e->result = ev->result; + if (ev->status > 0) + e->status = ev->status; + e->done = true; + e->seconds = std::chrono::duration(std::chrono::steady_clock::now() - e->started).count(); + t->pop(); + return; /* the caller lets go of the transfer: retire() */ + } + } + } + + struct Multi + { + std::vector handles; + std::vector started; + std::vector reported; /* its CURLMSG_DONE is out */ + std::vector messages; + size_t messageRead = 0; + }; +} + +CURLcode curl_global_init(long flags) +{ + (void)flags; + if (g_running) + return CURLE_OK; + if (!retro_eventcount_init(&g_wake)) + return CURLE_FAILED_INIT; + network_init(); + g_stopping.store(false, std::memory_order_relaxed); + g_thread = std::thread(transferThread); + g_running = true; + return CURLE_OK; +} + +void curl_global_cleanup(void) +{ + if (!g_running) + return; + g_stopping.store(true, std::memory_order_release); + wakeTransferThread(); + g_thread.join(); + retro_eventcount_free(&g_wake); + g_running = false; +} + +CURL *curl_easy_init(void) +{ + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) + return nullptr; + return new Easy(); +} + +void curl_easy_cleanup(CURL *curl) +{ + delete (Easy*)curl; +} + +void curl_easy_reset(CURL *curl) +{ + if (curl) + ((Easy*)curl)->reset(); +} + +CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) +{ + Easy *e = (Easy*)curl; + if (!e) + return CURLE_BAD_FUNCTION_ARGUMENT; + va_list ap; + va_start(ap, option); + long lv = 0; + void *pv = nullptr; + curl_off_t ov = 0; + const int opt = (int)option; + if (opt >= CURLOPTTYPE_OFF_T && opt < CURLOPTTYPE_BLOB) + ov = va_arg(ap, curl_off_t); + else if (opt >= CURLOPTTYPE_OBJECTPOINT) + pv = va_arg(ap, void*); + else + lv = va_arg(ap, long); + va_end(ap); + + switch (opt) + { + case CURLOPT_URL: e->url = pv ? (const char*)pv : ""; break; + case CURLOPT_CUSTOMREQUEST: e->customRequest = pv ? (const char*)pv : ""; break; + case CURLOPT_USERAGENT: e->userAgent = pv ? (const char*)pv : ""; break; + case CURLOPT_HTTPHEADER: + e->headers.clear(); + for (struct curl_slist *l = (struct curl_slist*)pv; l; l = l->next) + if (l->data) + e->headers.emplace_back(l->data); + break; + case CURLOPT_POST: e->post = lv != 0; if (e->post) e->noBody = false; break; + case CURLOPT_HTTPGET: if (lv) { e->post = false; e->noBody = false; } break; + case CURLOPT_NOBODY: e->noBody = lv != 0; break; + case CURLOPT_POSTFIELDS: e->postFields = pv; e->post = true; break; + case CURLOPT_HTTPPOST: e->httpPost = (const struct curl_httppost*)pv; if (pv) e->post = true; break; + case CURLOPT_POSTFIELDSIZE: e->postFieldSize = lv; break; + case CURLOPT_POSTFIELDSIZE_LARGE: e->postFieldSize = ov; break; + case CURLOPT_INFILESIZE: e->inFileSize = lv; break; + case CURLOPT_INFILESIZE_LARGE: e->inFileSize = ov; break; + case CURLOPT_TIMEOUT: e->timeoutMs = lv * 1000; break; + case CURLOPT_TIMEOUT_MS: e->timeoutMs = lv; break; + case CURLOPT_NOPROGRESS: e->noProgress = lv != 0; break; + case CURLOPT_SSL_VERIFYPEER: e->verifyPeer = lv != 0; break; + case CURLOPT_SSL_VERIFYHOST: e->verifyHost = lv != 0; break; + case CURLOPT_WRITEFUNCTION: e->writeFn = (curl_write_callback)pv; break; + case CURLOPT_WRITEDATA: e->writeData = pv; break; + case CURLOPT_HEADERFUNCTION: e->headerFn = (curl_write_callback)pv; break; + case CURLOPT_HEADERDATA: e->headerData = pv; break; + case CURLOPT_READFUNCTION: e->readFn = (curl_read_callback)pv; break; + case CURLOPT_READDATA: e->readData = pv; break; + case CURLOPT_PROGRESSFUNCTION: e->progressFn = (curl_progress_callback)pv; break; + case CURLOPT_PROGRESSDATA: e->progressData = pv; break; + case CURLOPT_SSL_CTX_FUNCTION: e->sslCtxFn = (curl_ssl_ctx_callback)pv; break; + case CURLOPT_SSL_CTX_DATA: e->sslCtxData = pv; break; + default: break; /* accepted, without effect */ + } + return CURLE_OK; +} + +CURLcode curl_easy_perform(CURL *curl) +{ + Easy *e = (Easy*)curl; + if (!e) + return CURLE_BAD_FUNCTION_ARGUMENT; + const CURLcode r = begin(curl, e); + if (r != CURLE_OK) + return r; + Transfer *t = e->xfer; + while (!e->done) + { + const int key = retro_eventcount_prepare_wait(&t->ec); + deliver(e); + /* sleep while nothing is left that can be handed over: an empty + * queue, or a paused one until curl_easy_pause() lifts it */ + if (!e->done && (!t->peek() || (e->pauseBits.load(std::memory_order_relaxed) & CURLPAUSE_RECV))) + retro_eventcount_commit_wait(&t->ec, key); + else + retro_eventcount_cancel_wait(&t->ec); + } + retire(e); + return e->result; +} + +CURLcode curl_easy_pause(CURL *curl, int bitmask) +{ + Easy *e = (Easy*)curl; + if (!e) + return CURLE_BAD_FUNCTION_ARGUMENT; + e->pauseBits.store(bitmask, std::memory_order_relaxed); + if (e->xfer) + { + e->xfer->recvPaused.store((bitmask & CURLPAUSE_RECV) != 0, std::memory_order_relaxed); + if (!(bitmask & CURLPAUSE_RECV)) + { + if (e->inMulti) + { + deliver(e); + retire(e); + } + else + retro_eventcount_notify(&e->xfer->ec); /* the thread in curl_easy_perform() */ + } + wakeTransferThread(); + } + return CURLE_OK; +} + +CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) +{ + Easy *e = (Easy*)curl; + if (!e) + return CURLE_BAD_FUNCTION_ARGUMENT; + va_list ap; + va_start(ap, info); + void *out = va_arg(ap, void*); + va_end(ap); + if (!out) + return CURLE_BAD_FUNCTION_ARGUMENT; + switch (info) + { + case CURLINFO_RESPONSE_CODE: *(long*)out = e->status; break; + case CURLINFO_SSL_VERIFYRESULT: *(long*)out = 0; break; + case CURLINFO_SIZE_DOWNLOAD: *(double*)out = e->downloaded; break; + case CURLINFO_SIZE_UPLOAD: *(double*)out = e->uploaded; break; + case CURLINFO_SPEED_DOWNLOAD: *(double*)out = e->seconds > 0 ? e->downloaded / e->seconds : 0; break; + case CURLINFO_SPEED_UPLOAD: *(double*)out = e->seconds > 0 ? e->uploaded / e->seconds : 0; break; + case CURLINFO_CONTENT_LENGTH_DOWNLOAD: *(double*)out = e->contentLength; break; + case CURLINFO_EFFECTIVE_URL: *(const char**)out = e->url.c_str(); break; + case CURLINFO_CONTENT_TYPE: *(const char**)out = e->contentType.empty() ? nullptr : e->contentType.c_str(); break; + case CURLINFO_REDIRECT_URL: *(const char**)out = nullptr; break; + default: + switch ((int)info & CURLINFO_TYPEMASK) + { + case CURLINFO_STRING: *(const char**)out = nullptr; break; + case CURLINFO_LONG: *(long*)out = 0; break; + case CURLINFO_DOUBLE: *(double*)out = 0; break; + case CURLINFO_OFF_T: *(curl_off_t*)out = 0; break; + default: return CURLE_UNKNOWN_OPTION; + } + } + return CURLE_OK; +} + +const char *curl_easy_strerror(CURLcode code) +{ + switch (code) + { + case CURLE_OK: return "No error"; + case CURLE_UNSUPPORTED_PROTOCOL: return "Unsupported protocol"; + case CURLE_FAILED_INIT: return "Failed initialization"; + case CURLE_URL_MALFORMAT: return "URL using bad/illegal format or missing URL"; + case CURLE_COULDNT_RESOLVE_HOST: return "Could not resolve hostname"; + case CURLE_COULDNT_CONNECT: return "Could not connect to server"; + case CURLE_WRITE_ERROR: return "Failed writing received data to disk/application"; + case CURLE_READ_ERROR: return "Failed to open/read local data from file/application"; + case CURLE_OUT_OF_MEMORY: return "Out of memory"; + case CURLE_OPERATION_TIMEDOUT: return "Timeout was reached"; + case CURLE_SSL_CONNECT_ERROR: return "SSL connect error"; + case CURLE_ABORTED_BY_CALLBACK: return "Operation was aborted by an application callback"; + case CURLE_TOO_MANY_REDIRECTS: return "Number of redirects hit maximum amount"; + case CURLE_GOT_NOTHING: return "Server returned nothing (no headers, no data)"; + case CURLE_SEND_ERROR: return "Failed sending data to the peer"; + case CURLE_RECV_ERROR: return "Failure when receiving data from the peer"; + case CURLE_SSL_CERTPROBLEM: return "Problem with the local SSL certificate"; + case CURLE_PEER_FAILED_VERIFICATION: return "SSL peer certificate or SSH remote key was not OK"; + default: return "Unknown error"; + } +} + +struct curl_slist *curl_slist_append(struct curl_slist *list, const char *data) +{ + struct curl_slist *n = (struct curl_slist*)malloc(sizeof(*n)); + if (!n) + return nullptr; + n->data = strdup(data ? data : ""); + n->next = nullptr; + if (!list) + return n; + struct curl_slist *l = list; + while (l->next) + l = l->next; + l->next = n; + return list; +} + +void curl_slist_free_all(struct curl_slist *list) +{ + while (list) + { + struct curl_slist *n = list->next; + free(list->data); + free(list); + list = n; + } +} + +CURLM *curl_multi_init(void) +{ + return new Multi(); +} + +CURLMcode curl_multi_cleanup(CURLM *multi) +{ + Multi *m = (Multi*)multi; + if (!m) + return CURLM_BAD_HANDLE; + for (Easy *e : m->handles) + e->inMulti = false; + delete m; + return CURLM_OK; +} + +CURLMcode curl_multi_add_handle(CURLM *multi, CURL *curl) +{ + Multi *m = (Multi*)multi; + Easy *e = (Easy*)curl; + if (!m) + return CURLM_BAD_HANDLE; + if (!e || e->inMulti) + return CURLM_BAD_EASY_HANDLE; + e->inMulti = true; + m->handles.push_back(e); + m->started.push_back(false); + m->reported.push_back(false); + return CURLM_OK; +} + +CURLMcode curl_multi_remove_handle(CURLM *multi, CURL *curl) +{ + Multi *m = (Multi*)multi; + if (!m) + return CURLM_BAD_HANDLE; + for (size_t i = 0; i < m->handles.size(); i++) + if (m->handles[i] == (Easy*)curl) + { + m->handles[i]->abandon(); + m->handles[i]->inMulti = false; + m->handles.erase(m->handles.begin() + i); + m->started.erase(m->started.begin() + i); + m->reported.erase(m->reported.begin() + i); + return CURLM_OK; + } + return CURLM_BAD_EASY_HANDLE; +} + +CURLMcode curl_multi_perform(CURLM *multi, int *running_handles) +{ + Multi *m = (Multi*)multi; + if (!m) + return CURLM_BAD_HANDLE; + int running = 0; + for (size_t i = 0; i < m->handles.size(); i++) + { + Easy *e = m->handles[i]; + if (!m->started[i]) + { + m->started[i] = true; + const CURLcode r = begin(e, e); + if (r != CURLE_OK) + { + e->done = true; + e->result = r; + } + } + if (!e->done) + { + deliver(e); + retire(e); + } + /* done here, or earlier in a curl_easy_pause() that delivered */ + if (e->done && !m->reported[i]) + { + m->reported[i] = true; + CURLMsg msg{}; + msg.msg = CURLMSG_DONE; + msg.easy_handle = e; + msg.data.result = e->result; + m->messages.push_back(msg); + } + if (!e->done) + running++; + } + if (running_handles) + *running_handles = running; + return CURLM_OK; +} + +CURLMcode curl_multi_timeout(CURLM *multi, long *milliseconds) +{ + Multi *m = (Multi*)multi; + if (!m || !milliseconds) + return CURLM_BAD_HANDLE; + long t = -1; + for (size_t i = 0; i < m->handles.size(); i++) + { + Easy *e = m->handles[i]; + if (!m->started[i] || (e->done && !m->reported[i]) + || (e->xfer && e->xfer->peek() && !(e->pauseBits.load(std::memory_order_relaxed) & CURLPAUSE_RECV))) + { + t = 0; + break; + } + if (!e->done) + t = 10; + } + *milliseconds = t; + return CURLM_OK; +} + +CURLMcode curl_multi_setopt(CURLM *multi, CURLMoption option, ...) +{ + (void)option; + return multi ? CURLM_OK : CURLM_BAD_HANDLE; +} + +CURLMcode curl_multi_fdset(CURLM *multi, void *read_fd_set, void *write_fd_set, void *exc_fd_set, int *max_fd) +{ + (void)read_fd_set; (void)write_fd_set; (void)exc_fd_set; + if (!multi) + return CURLM_BAD_HANDLE; + if (max_fd) + *max_fd = -1; + return CURLM_OK; +} + +CURLMsg *curl_multi_info_read(CURLM *multi, int *msgs_in_queue) +{ + Multi *m = (Multi*)multi; + if (!m || m->messageRead >= m->messages.size()) + { + if (m) + { + m->messages.clear(); + m->messageRead = 0; + } + if (msgs_in_queue) + *msgs_in_queue = 0; + return nullptr; + } + CURLMsg *msg = &m->messages[m->messageRead++]; + if (msgs_in_queue) + *msgs_in_queue = (int)(m->messages.size() - m->messageRead); + return msg; +} + +namespace +{ + /* a form part's own storage: the struct, then what was copied */ + struct FormPart + { + struct curl_httppost post; + std::string name; + std::string contents; + std::string contentType; + }; +} + +CURLFORMcode curl_formadd(struct curl_httppost **httppost, struct curl_httppost **last_post, ...) +{ + if (!httppost || !last_post) + return CURL_FORMADD_NULL; + FormPart *part = new FormPart(); + memset(&part->post, 0, sizeof(part->post)); + const char *name = nullptr, *contents = nullptr; + long nameLen = -1, contentsLen = -1; + bool copyName = false, copyContents = false; + CURLFORMcode r = CURL_FORMADD_OK; + va_list ap; + va_start(ap, last_post); + for (int opt = va_arg(ap, int); opt != CURLFORM_END && r == CURL_FORMADD_OK; opt = va_arg(ap, int)) + { + switch (opt) + { + case CURLFORM_COPYNAME: + case CURLFORM_PTRNAME: + if (name) + r = CURL_FORMADD_OPTION_TWICE; + name = va_arg(ap, const char*); + copyName = opt == CURLFORM_COPYNAME; + break; + case CURLFORM_NAMELENGTH: + nameLen = va_arg(ap, long); + break; + case CURLFORM_COPYCONTENTS: + case CURLFORM_PTRCONTENTS: + if (contents) + r = CURL_FORMADD_OPTION_TWICE; + contents = va_arg(ap, const char*); + copyContents = opt == CURLFORM_COPYCONTENTS; + break; + case CURLFORM_CONTENTSLENGTH: + contentsLen = va_arg(ap, long); + break; + case CURLFORM_CONTENTTYPE: + part->contentType = va_arg(ap, const char*); + break; + default: + r = CURL_FORMADD_UNKNOWN_OPTION; + } + } + va_end(ap); + if (r == CURL_FORMADD_OK && !name) + r = CURL_FORMADD_INCOMPLETE; + if (r != CURL_FORMADD_OK) + { + delete part; + return r; + } + /* the part keeps its own copy whichever was asked for: the caller's + * buffer may be gone by the time the request is built */ + (void)copyName; + (void)copyContents; + part->name.assign(name, nameLen >= 0 ? (size_t)nameLen : strlen(name)); + if (contents) + part->contents.assign(contents, contentsLen >= 0 ? (size_t)contentsLen : strlen(contents)); + part->post.name = &part->name[0]; + part->post.namelength = (long)part->name.size(); + part->post.contents = contents ? &part->contents[0] : nullptr; + part->post.contentslength = (long)part->contents.size(); + part->post.contenttype = part->contentType.empty() ? nullptr : &part->contentType[0]; + if (*last_post) + (*last_post)->next = &part->post; + else + *httppost = &part->post; + *last_post = &part->post; + return CURL_FORMADD_OK; +} + +void curl_formfree(struct curl_httppost *form) +{ + while (form) + { + struct curl_httppost *next = form->next; + delete (FormPart*)form; /* post is FormPart's first member */ + form = next; + } +} + +CURLSH *curl_share_init(void) +{ + /* nothing is shared between handles that the transfer thread does + * not share already */ + return malloc(1); +} + +CURLSHcode curl_share_setopt(CURLSH *share, CURLSHoption option, ...) +{ + (void)option; + return share ? CURLSHE_OK : CURLSHE_INVALID; +} + +CURLSHcode curl_share_cleanup(CURLSH *share) +{ + free(share); + return CURLSHE_OK; +} diff --git a/dependencies/curl-compat/include/curl/curl.h b/dependencies/curl-compat/include/curl/curl.h new file mode 100644 index 00000000..3be90de7 --- /dev/null +++ b/dependencies/curl-compat/include/curl/curl.h @@ -0,0 +1,365 @@ +/* The part of libcurl's API the core uses, carried by libretro-common's + * net_http and its built-in TLS client (curl_compat.cpp), so that no + * libcurl, OpenSSL or mbedTLS is linked. + * + * Option, info and error numbers are libcurl's own: nlibcurl hands the + * guest's numbers straight through. Options this layer has no use for + * are accepted and ignored, as libcurl does with ones it was built + * without. + * + * Differences from libcurl: + * - CURLOPT_SSL_CTX_FUNCTION receives a struct curl_tls_settings * + * where libcurl passes the TLS library's context; fill it with the + * curl_tls_* functions below. + * - CURLOPT_CLOSESOCKETFUNCTION and CURLOPT_SOCKOPTFUNCTION are never + * called: the sockets belong to net_http. + * - A request body from CURLOPT_READFUNCTION is read whole before the + * request goes out. + * - curl_multi_fdset() reports no descriptors; curl_multi_timeout() + * says when to call curl_multi_perform() again. + * - Redirects are followed whatever CURLOPT_FOLLOWLOCATION says, and a + * proxy is not used. + * - curl_formadd() takes names and contents (with their lengths) and a + * content type; files and buffers are refused. */ +#ifndef CURL_COMPAT_CURL_H +#define CURL_COMPAT_CURL_H + +#include +#include + +#ifdef _WIN32 +#include +typedef SOCKET curl_socket_t; +#else +typedef int curl_socket_t; +#endif + +typedef void CURL; +typedef void CURLSH; +typedef void CURLM; +typedef int64_t curl_off_t; + +struct curl_slist +{ + char *data; + struct curl_slist *next; +}; + +#define CURLOPTTYPE_LONG 0 +#define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_FUNCTIONPOINT 20000 +#define CURLOPTTYPE_OFF_T 30000 +#define CURLOPTTYPE_BLOB 40000 + +typedef enum +{ + CURLOPT_WRITEDATA = 10001, + CURLOPT_URL = 10002, + CURLOPT_PROXY = 10004, + CURLOPT_READDATA = 10009, + CURLOPT_TIMEOUT = 13, + CURLOPT_INFILESIZE = 14, + CURLOPT_POSTFIELDS = 10015, + CURLOPT_USERAGENT = 10018, + CURLOPT_LOW_SPEED_LIMIT = 19, + CURLOPT_LOW_SPEED_TIME = 20, + CURLOPT_HTTPHEADER = 10023, + CURLOPT_HTTPPOST = 10024, + CURLOPT_HEADERDATA = 10029, + CURLOPT_SSLVERSION = 32, + CURLOPT_CUSTOMREQUEST = 10036, + CURLOPT_VERBOSE = 41, + CURLOPT_NOPROGRESS = 43, + CURLOPT_NOBODY = 44, + CURLOPT_POST = 47, + CURLOPT_FOLLOWLOCATION = 52, + CURLOPT_PROGRESSDATA = 10057, + CURLOPT_POSTFIELDSIZE = 60, + CURLOPT_SSL_VERIFYPEER = 64, + CURLOPT_MAXREDIRS = 68, + CURLOPT_CONNECTTIMEOUT = 78, + CURLOPT_HTTPGET = 80, + CURLOPT_SSL_VERIFYHOST = 81, + CURLOPT_HTTP_VERSION = 84, + CURLOPT_BUFFERSIZE = 98, + CURLOPT_NOSIGNAL = 99, + CURLOPT_SHARE = 10100, + CURLOPT_SSL_CTX_DATA = 10109, + CURLOPT_SOCKOPTDATA = 10149, + CURLOPT_TIMEOUT_MS = 155, + CURLOPT_CONNECTTIMEOUT_MS = 156, + CURLOPT_CLOSESOCKETDATA = 10209, + CURLOPT_WRITEFUNCTION = 20011, + CURLOPT_READFUNCTION = 20012, + CURLOPT_PROGRESSFUNCTION = 20056, + CURLOPT_HEADERFUNCTION = 20079, + CURLOPT_SSL_CTX_FUNCTION = 20108, + CURLOPT_SOCKOPTFUNCTION = 20148, + CURLOPT_CLOSESOCKETFUNCTION = 20208, + CURLOPT_INFILESIZE_LARGE = 30115, + CURLOPT_POSTFIELDSIZE_LARGE = 30120, + CURLOPT_MAX_SEND_SPEED_LARGE = 30145, + CURLOPT_MAX_RECV_SPEED_LARGE = 30146 +} CURLoption; + +#define CURLOPT_FILE CURLOPT_WRITEDATA + +typedef enum +{ + CURLE_OK = 0, + CURLE_UNSUPPORTED_PROTOCOL = 1, + CURLE_FAILED_INIT = 2, + CURLE_URL_MALFORMAT = 3, + CURLE_COULDNT_RESOLVE_HOST = 6, + CURLE_COULDNT_CONNECT = 7, + CURLE_WRITE_ERROR = 23, + CURLE_READ_ERROR = 26, + CURLE_OUT_OF_MEMORY = 27, + CURLE_OPERATION_TIMEDOUT = 28, + CURLE_SSL_CONNECT_ERROR = 35, + CURLE_ABORTED_BY_CALLBACK = 42, + CURLE_BAD_FUNCTION_ARGUMENT = 43, + CURLE_TOO_MANY_REDIRECTS = 47, + CURLE_UNKNOWN_OPTION = 48, + CURLE_GOT_NOTHING = 52, + CURLE_SEND_ERROR = 55, + CURLE_RECV_ERROR = 56, + CURLE_SSL_CERTPROBLEM = 58, + CURLE_PEER_FAILED_VERIFICATION = 60 +} CURLcode; + +#define CURLE_SSL_CACERT CURLE_PEER_FAILED_VERIFICATION + +#define CURLINFO_STRING 0x100000 +#define CURLINFO_LONG 0x200000 +#define CURLINFO_DOUBLE 0x300000 +#define CURLINFO_OFF_T 0x600000 +#define CURLINFO_TYPEMASK 0xf00000 + +typedef enum +{ + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, + CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_REDIRECT_URL = CURLINFO_STRING + 31 +} CURLINFO; + +typedef enum +{ + CURLM_OK = 0, + CURLM_BAD_HANDLE = 1, + CURLM_BAD_EASY_HANDLE = 2, + CURLM_OUT_OF_MEMORY = 3, + CURLM_BAD_FUNCTION_ARGUMENT = 10 +} CURLMcode; + +typedef enum +{ + CURLMOPT_MAXCONNECTS = 6 +} CURLMoption; + +typedef enum +{ + CURLMSG_NONE, + CURLMSG_DONE +} CURLMSG; + +typedef struct CURLMsg +{ + CURLMSG msg; + CURL *easy_handle; + union + { + void *whatever; + CURLcode result; + } data; +} CURLMsg; + +typedef enum +{ + CURLSHE_OK, + CURLSHE_BAD_OPTION, + CURLSHE_IN_USE, + CURLSHE_INVALID, + CURLSHE_NOMEM +} CURLSHcode; + +typedef enum +{ + CURLSHOPT_NONE, + CURLSHOPT_SHARE, + CURLSHOPT_UNSHARE, + CURLSHOPT_LOCKFUNC, + CURLSHOPT_UNLOCKFUNC, + CURLSHOPT_USERDATA +} CURLSHoption; + +typedef enum +{ + CURL_LOCK_DATA_NONE, + CURL_LOCK_DATA_SHARE, + CURL_LOCK_DATA_COOKIE, + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_SSL_SESSION, + CURL_LOCK_DATA_CONNECT +} curl_lock_data; + +typedef enum +{ + CURL_LOCK_ACCESS_NONE, + CURL_LOCK_ACCESS_SHARED, + CURL_LOCK_ACCESS_SINGLE +} curl_lock_access; + +typedef enum +{ + CURLSOCKTYPE_IPCXN, + CURLSOCKTYPE_ACCEPT +} curlsocktype; + +#define CURL_GLOBAL_SSL (1 << 0) +#define CURL_GLOBAL_WIN32 (1 << 1) +#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL | CURL_GLOBAL_WIN32) +#define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL + +#define CURL_HTTP_VERSION_1_1 2L +#define CURL_SSLVERSION_DEFAULT 0 +#define CURL_SSLVERSION_SSLv3 3 +#define CURL_SSLVERSION_TLSv1_2 6 + +#define CURL_WRITEFUNC_PAUSE 0x10000001 +#define CURL_READFUNC_ABORT 0x10000000 +#define CURL_READFUNC_PAUSE 0x10000001 +#define CURLPAUSE_RECV (1 << 0) +#define CURLPAUSE_SEND (1 << 2) +#define CURLPAUSE_ALL (CURLPAUSE_RECV | CURLPAUSE_SEND) +#define CURLPAUSE_CONT 0 + +typedef size_t (*curl_write_callback)(char *buffer, size_t size, + size_t nitems, void *userdata); +typedef size_t (*curl_read_callback)(char *buffer, size_t size, + size_t nitems, void *instream); +typedef int (*curl_progress_callback)(void *clientp, + double dltotal, double dlnow, double ultotal, double ulnow); +typedef int (*curl_sockopt_callback)(void *clientp, + curl_socket_t curlfd, curlsocktype purpose); +typedef int (*curl_closesocket_callback)(void *clientp, + curl_socket_t item); +typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, void *ssl_ctx, + void *userptr); +typedef void (*curl_lock_function)(CURL *handle, curl_lock_data data, + curl_lock_access locktype, void *userptr); +typedef void (*curl_unlock_function)(CURL *handle, curl_lock_data data, + void *userptr); +typedef void *(*curl_malloc_callback)(size_t size); +typedef void (*curl_free_callback)(void *ptr); +typedef void *(*curl_realloc_callback)(void *ptr, size_t size); +typedef char *(*curl_strdup_callback)(const char *str); +typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size); + +CURLcode curl_global_init(long flags); +/* Stops the transfer thread; call when nothing is transferring. */ +void curl_global_cleanup(void); + +CURL *curl_easy_init(void); +void curl_easy_cleanup(CURL *curl); +void curl_easy_reset(CURL *curl); +CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); +CURLcode curl_easy_perform(CURL *curl); +CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); +CURLcode curl_easy_pause(CURL *curl, int bitmask); +const char *curl_easy_strerror(CURLcode code); + +struct curl_slist *curl_slist_append(struct curl_slist *list, + const char *data); +void curl_slist_free_all(struct curl_slist *list); + +CURLM *curl_multi_init(void); +CURLMcode curl_multi_cleanup(CURLM *multi); +CURLMcode curl_multi_add_handle(CURLM *multi, CURL *curl); +CURLMcode curl_multi_remove_handle(CURLM *multi, CURL *curl); +CURLMcode curl_multi_perform(CURLM *multi, int *running_handles); +CURLMcode curl_multi_timeout(CURLM *multi, long *milliseconds); +CURLMcode curl_multi_setopt(CURLM *multi, CURLMoption option, ...); +CURLMcode curl_multi_fdset(CURLM *multi, void *read_fd_set, + void *write_fd_set, void *exc_fd_set, int *max_fd); +CURLMsg *curl_multi_info_read(CURLM *multi, int *msgs_in_queue); + +/* multipart/form-data, for CURLOPT_HTTPPOST */ +struct curl_httppost +{ + struct curl_httppost *next; + char *name; + long namelength; + char *contents; + long contentslength; + char *contenttype; +}; + +typedef enum +{ + CURLFORM_NOTHING, + CURLFORM_COPYNAME, + CURLFORM_PTRNAME, + CURLFORM_NAMELENGTH, + CURLFORM_COPYCONTENTS, + CURLFORM_PTRCONTENTS, + CURLFORM_CONTENTSLENGTH, + CURLFORM_FILECONTENT, + CURLFORM_ARRAY, + CURLFORM_OBSOLETE, + CURLFORM_FILE, + CURLFORM_BUFFER, + CURLFORM_BUFFERPTR, + CURLFORM_BUFFERLENGTH, + CURLFORM_CONTENTTYPE, + CURLFORM_CONTENTHEADER, + CURLFORM_FILENAME, + CURLFORM_END +} CURLformoption; + +typedef enum +{ + CURL_FORMADD_OK, + CURL_FORMADD_MEMORY, + CURL_FORMADD_OPTION_TWICE, + CURL_FORMADD_NULL, + CURL_FORMADD_UNKNOWN_OPTION, + CURL_FORMADD_INCOMPLETE, + CURL_FORMADD_ILLEGAL_ARRAY, + CURL_FORMADD_DISABLED +} CURLFORMcode; + +CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, ...); +void curl_formfree(struct curl_httppost *form); + +CURLSH *curl_share_init(void); +CURLSHcode curl_share_setopt(CURLSH *share, CURLSHoption option, ...); +CURLSHcode curl_share_cleanup(CURLSH *share); + +/* TLS settings for one transfer, filled in by its + * CURLOPT_SSL_CTX_FUNCTION. Certificates and keys are DER and copied. */ +struct curl_tls_settings; + +/* A trust anchor in place of the built-in bundle. */ +bool curl_tls_add_trust(struct curl_tls_settings *tls, + const void *der, size_t len); +/* The client certificate and its RSA key (PKCS#1 or PKCS#8). */ +bool curl_tls_set_client(struct curl_tls_settings *tls, + const void *cert_der, size_t cert_len, + const void *key_der, size_t key_len); +/* Suites to offer, by IANA number, most preferred first. */ +void curl_tls_set_suites(struct curl_tls_settings *tls, + const unsigned *suites, size_t count); +/* Whether the peer's certificate and name are checked. */ +void curl_tls_set_verify(struct curl_tls_settings *tls, + bool peer, bool host); + +#endif diff --git a/dependencies/curl-compat/test/.gitignore b/dependencies/curl-compat/test/.gitignore new file mode 100644 index 00000000..c4539e7f --- /dev/null +++ b/dependencies/curl-compat/test/.gitignore @@ -0,0 +1,3 @@ +curl_compat_test +curl_compat_test.exe +*.o diff --git a/dependencies/curl-compat/test/Makefile b/dependencies/curl-compat/test/Makefile new file mode 100644 index 00000000..deb8cf86 --- /dev/null +++ b/dependencies/curl-compat/test/Makefile @@ -0,0 +1,29 @@ +# The curl_compat test (run_tests.sh); SANITIZER=address,undefined or +# SANITIZER=thread for the sanitizer runs. +LRC := ../../libretro-common +CXX ?= g++ +CC ?= gcc +FLAGS := -O1 -g -I../include -I$(LRC)/include -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_CRYPTO -DHAVE_THREADS +ifneq ($(SANITIZER),) + FLAGS += -fsanitize=$(SANITIZER) -fno-omit-frame-pointer +endif +LRC_SOURCES := $(addprefix $(LRC)/,net/net_http.c net/net_socket.c net/net_compat.c \ + net/net_socket_ssl_retro.c crypto/crypto.c crypto/kdf.c crypto/pk.c crypto/x509.c \ + hash/lrc_hash.c utils/md5.c encodings/encoding_base64.c streams/file_stream.c \ + vfs/vfs_implementation.c file/file_path.c file/file_path_io.c string/rstrtod.c \ + string/stdstring.c encodings/encoding_utf.c features/features_cpu.c compat/compat_strl.c \ + compat/fopen_utf8.c compat/compat_strcasestr.c compat/compat_posix_string.c time/rtime.c \ + lists/string_list.c file/retro_dirent.c rthreads/rthreads.c rthreads/retro_eventcount.c) +LIBS := -lpthread +ifneq (,$(findstring mingw,$(CC))) + LIBS += -lws2_32 -liphlpapi -lbcrypt -static + EXE := .exe +endif + +curl_compat_test$(EXE): curl_compat_test.cpp ../curl_compat.cpp $(LRC_SOURCES) + $(CC) $(FLAGS) -std=gnu11 -c $(LRC_SOURCES) + $(CXX) $(FLAGS) -std=gnu++20 curl_compat_test.cpp ../curl_compat.cpp *.o $(LIBS) -o $@ + rm -f *.o + +clean: + rm -f curl_compat_test curl_compat_test.exe *.o diff --git a/dependencies/curl-compat/test/curl_compat_test.cpp b/dependencies/curl-compat/test/curl_compat_test.cpp new file mode 100644 index 00000000..9f27f47b --- /dev/null +++ b/dependencies/curl-compat/test/curl_compat_test.cpp @@ -0,0 +1,338 @@ +/* curl_compat against local servers (run_tests.sh starts them): + * curl_compat_test + * holds body.bin and its SHA-256 in body.sha256, and for the TLS + * cases ca.der, cli.der and cli.key.der. Exit 0 on success. */ +#include + +#include +#include +#include +#include +#include +#include + +#include + +static std::string g_dir; +static int g_http, g_https; + +static std::vector slurp(const std::string &path) +{ + std::vector v; + FILE *f = fopen(path.c_str(), "rb"); + if (!f) + return v; + char buf[65536]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) + v.insert(v.end(), buf, buf + n); + fclose(f); + return v; +} + +static std::string sha256hex(const std::vector &v) +{ + char hex[65]; + sha256_hash(hex, v.data(), v.size()); + return hex; +} + +static std::string wantSha() +{ + std::vector s = slurp(g_dir + "/body.sha256"); + return std::string(s.begin(), s.begin() + 64); +} + +static std::string url(bool tls, const char *path) +{ + return std::string(tls ? "https://localhost:" : "http://127.0.0.1:") + + std::to_string(tls ? g_https : g_http) + path; +} + +struct Sink +{ + std::vector body; + std::string headers; + int pauses = 0; /* pause this many times before taking data */ + int paused = 0; +}; + +static size_t onWrite(char *p, size_t s, size_t n, void *u) +{ + Sink *k = (Sink*)u; + if (k->paused < k->pauses) + { + k->paused++; + return CURL_WRITEFUNC_PAUSE; + } + k->body.insert(k->body.end(), p, p + s * n); + return s * n; +} + +static size_t onHeader(char *p, size_t s, size_t n, void *u) +{ + ((Sink*)u)->headers.append(p, s * n); + return s * n; +} + +#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #c); return 1; } } while (0) + +static int getBody(bool tls, curl_ssl_ctx_callback ctxFn = nullptr) +{ + Sink k; + CURL *c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, url(tls, "/body.bin").c_str()); + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, onWrite); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &k); + curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, onHeader); + curl_easy_setopt(c, CURLOPT_HEADERDATA, &k); + if (ctxFn) + { + curl_easy_setopt(c, CURLOPT_SSL_CTX_FUNCTION, ctxFn); + curl_easy_setopt(c, CURLOPT_SSL_CTX_DATA, nullptr); + } + const CURLcode r = curl_easy_perform(c); + long status = 0; + double size = 0; + curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &status); + curl_easy_getinfo(c, CURLINFO_SIZE_DOWNLOAD, &size); + curl_easy_cleanup(c); + if (r != CURLE_OK) + fprintf(stderr, "perform: %s\n", curl_easy_strerror(r)); + CHECK(r == CURLE_OK); + CHECK(status == 200); + CHECK(k.headers.compare(0, 13, "HTTP/1.1 200 ") == 0 || k.headers.compare(0, 13, "HTTP/1.1 200\r") == 0); + CHECK(k.headers.size() >= 4 && k.headers.compare(k.headers.size() - 4, 4, "\r\n\r\n") == 0); + CHECK((size_t)size == k.body.size()); + CHECK(sha256hex(k.body) == wantSha()); + return 0; +} + +static int postEcho(bool useRead) +{ + /* the server answers a POST with the SHA-256 of what it got */ + std::vector body = slurp(g_dir + "/body.bin"); + body.resize(300001); + struct Reader { const std::vector *v; size_t off; } rd{&body, 0}; + Sink k; + CURL *c = curl_easy_init(); + struct curl_slist *h = curl_slist_append(nullptr, "Content-Type: application/octet-stream"); + h = curl_slist_append(h, "Content-Length: 1"); /* replaced, as nlibcurl relies on */ + h = curl_slist_append(h, "X-Test: yes"); + curl_easy_setopt(c, CURLOPT_URL, url(false, "/echo").c_str()); + curl_easy_setopt(c, CURLOPT_HTTPHEADER, h); + curl_easy_setopt(c, CURLOPT_POST, 1L); + if (useRead) + { + curl_easy_setopt(c, CURLOPT_READFUNCTION, (curl_read_callback)[](char *p, size_t s, size_t n, void *u) -> size_t { + Reader *r = (Reader*)u; + size_t take = std::min(s * n, r->v->size() - r->off); + memcpy(p, r->v->data() + r->off, take); + r->off += take; + return take; + }); + curl_easy_setopt(c, CURLOPT_READDATA, &rd); + curl_easy_setopt(c, CURLOPT_INFILESIZE_LARGE, (curl_off_t)body.size()); + } + else + { + curl_easy_setopt(c, CURLOPT_POSTFIELDS, body.data()); + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)body.size()); + } + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, onWrite); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &k); + const CURLcode r = curl_easy_perform(c); + curl_easy_cleanup(c); + curl_slist_free_all(h); + CHECK(r == CURLE_OK); + CHECK(std::string(k.body.begin(), k.body.end()) == sha256hex(body) + " yes application/octet-stream"); + return 0; +} + +static int form() +{ + /* multipart/form-data from curl_formadd(), as nn_olv uploads */ + struct curl_httppost *post = nullptr, *last = nullptr; + std::string desc = "a description"; + std::string bin("\0\1\2--------------------------curlcompat", 40); + CHECK(curl_formadd(&post, &last, CURLFORM_COPYNAME, "description", CURLFORM_PTRCONTENTS, desc.data(), + CURLFORM_CONTENTSLENGTH, (long)desc.size(), CURLFORM_END) == CURL_FORMADD_OK); + CHECK(curl_formadd(&post, &last, CURLFORM_COPYNAME, "app_data", CURLFORM_PTRCONTENTS, bin.data(), + CURLFORM_CONTENTSLENGTH, (long)bin.size(), CURLFORM_END) == CURL_FORMADD_OK); + CHECK(curl_formadd(&post, &last, CURLFORM_FILE, "x", CURLFORM_END) == CURL_FORMADD_UNKNOWN_OPTION); + desc[0] = 'X'; /* the form kept its own copy */ + Sink k; + CURL *c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, url(false, "/echo").c_str()); + curl_easy_setopt(c, CURLOPT_HTTPPOST, post); + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, onWrite); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &k); + const CURLcode r = curl_easy_perform(c); + curl_easy_cleanup(c); + curl_formfree(post); + CHECK(r == CURLE_OK); + /* the boundary grew past the one the binary part contains */ + const std::string b = "------------------------curlcompatx"; + const std::string want = "--" + b + "\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\na description\r\n" + + "--" + b + "\r\nContent-Disposition: form-data; name=\"app_data\"\r\n\r\n" + bin + "\r\n--" + b + "--\r\n"; + CHECK(std::string(k.body.begin(), k.body.end()) + == sha256hex(std::vector(want.begin(), want.end())) + " None multipart/form-data; boundary=" + b); + return 0; +} + +static int notFound() +{ + Sink k; + CURL *c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, url(false, "/missing").c_str()); + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, onWrite); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &k); + const CURLcode r = curl_easy_perform(c); + long status = 0; + curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &status); + curl_easy_cleanup(c); + CHECK(r == CURLE_OK); + CHECK(status == 404); + return 0; +} + +static int timeout() +{ + CURL *c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, url(false, "/stall").c_str()); + curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, 700L); + const CURLcode r = curl_easy_perform(c); + curl_easy_cleanup(c); + CHECK(r == CURLE_OPERATION_TIMEDOUT); + return 0; +} + +static int multi(int pauses) +{ + const int N = 4; + Sink k[N]; + CURL *c[N]; + CURLM *m = curl_multi_init(); + for (int i = 0; i < N; i++) + { + k[i].pauses = pauses; + c[i] = curl_easy_init(); + curl_easy_setopt(c[i], CURLOPT_URL, url(false, "/body.bin").c_str()); + curl_easy_setopt(c[i], CURLOPT_WRITEFUNCTION, onWrite); + curl_easy_setopt(c[i], CURLOPT_WRITEDATA, &k[i]); + CHECK(curl_multi_add_handle(m, c[i]) == CURLM_OK); + } + int running = N, done = 0; + while (done < N) + { + curl_multi_perform(m, &running); + CURLMsg *msg; + int left; + while ((msg = curl_multi_info_read(m, &left))) + { + CHECK(msg->msg == CURLMSG_DONE); + CHECK(msg->data.result == CURLE_OK); + done++; + } + /* a paused transfer resumes when asked, as a game would */ + for (int i = 0; i < N; i++) + if (k[i].paused && k[i].paused <= k[i].pauses) + curl_easy_pause(c[i], CURLPAUSE_CONT); + long t = 0; + curl_multi_timeout(m, &t); + if (t > 0) + std::this_thread::sleep_for(std::chrono::milliseconds(t)); + } + CHECK(running == 0); + for (int i = 0; i < N; i++) + { + CHECK(sha256hex(k[i].body) == wantSha()); + CHECK(k[i].paused == pauses); + curl_multi_remove_handle(m, c[i]); + curl_easy_cleanup(c[i]); + } + curl_multi_cleanup(m); + return 0; +} + +static CURLcode tlsCtx(CURL *, void *tls, void *) +{ + std::vector ca = slurp(g_dir + "/ca.der"); + std::vector cert = slurp(g_dir + "/cli.der"); + std::vector key = slurp(g_dir + "/cli.key.der"); + static const unsigned suite = 0x0035; /* TLS_RSA_WITH_AES_256_CBC_SHA */ + if (!curl_tls_add_trust((curl_tls_settings*)tls, ca.data(), ca.size()) + || !curl_tls_set_client((curl_tls_settings*)tls, cert.data(), cert.size(), key.data(), key.size())) + return CURLE_SSL_CERTPROBLEM; + curl_tls_set_suites((curl_tls_settings*)tls, &suite, 1); + return CURLE_OK; +} + +static CURLcode tlsWrongCa(CURL *, void *tls, void *) +{ + std::vector other = slurp(g_dir + "/cli.der"); /* signed nothing the server shows */ + curl_tls_add_trust((curl_tls_settings*)tls, other.data(), other.size()); + return CURLE_OK; +} + +static int tlsRefused() +{ + CURL *c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, url(true, "/body.bin").c_str()); + curl_easy_setopt(c, CURLOPT_SSL_CTX_FUNCTION, tlsWrongCa); + const CURLcode r = curl_easy_perform(c); + curl_easy_cleanup(c); + CHECK(r == CURLE_SSL_CONNECT_ERROR); + return 0; +} + +static int threads() +{ + /* several threads at once, each with its own handle, as the IOSU + * services and the guest's nlibcurl workers do */ + int fails = 0; + std::vector t; + for (int i = 0; i < 6; i++) + t.emplace_back([&fails, i] { if ((i & 1) ? postEcho(true) : getBody(false)) fails++; }); + for (auto &x : t) + x.join(); + CHECK(fails == 0); + return 0; +} + +static int restart() +{ + /* the transfer thread stops at curl_global_cleanup() and comes back */ + CHECK(getBody(false) == 0); + curl_global_cleanup(); + CHECK(getBody(false) == 0); + curl_global_cleanup(); + curl_global_cleanup(); + return 0; +} + +int main(int argc, char **argv) +{ + if (argc != 5) + return 2; + g_http = atoi(argv[2]); + g_https = atoi(argv[3]); + g_dir = argv[4]; + const std::string t = argv[1]; + int r = 2; + curl_global_init(CURL_GLOBAL_DEFAULT); /* as retro_init() does */ + if (t == "get") r = getBody(false); + else if (t == "post") r = postEcho(false); + else if (t == "post-read") r = postEcho(true); + else if (t == "404") r = notFound(); + else if (t == "form") r = form(); + else if (t == "timeout") r = timeout(); + else if (t == "multi") r = multi(0); + else if (t == "pause") r = multi(2); + else if (t == "tls") r = getBody(true, tlsCtx); + else if (t == "tls-refused") r = tlsRefused(); + else if (t == "threads") r = threads(); + else if (t == "restart") r = restart(); + curl_global_cleanup(); + return r; +} diff --git a/dependencies/curl-compat/test/run_tests.sh b/dependencies/curl-compat/test/run_tests.sh new file mode 100755 index 00000000..e93184ac --- /dev/null +++ b/dependencies/curl-compat/test/run_tests.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# curl_compat against local servers: GET, POST from a buffer, from a +# read callback and as a multipart form, a 404, a timeout, the multi interface with and without +# paused transfers, several threads at once, the transfer thread stopped +# and started again, and TLS with the client certificate and the +# RSA/AES-256-CBC suite nlibcurl asks for, against a server that requires +# both - and refused with a trust anchor that signed nothing. Needs +# python3 and openssl. RUNNER prefixes the test (wine for a MinGW build). +set -e +cd "$(dirname "$0")" +D=$(mktemp -d) +trap 'kill $HTTP $TLS 2>/dev/null || true; rm -rf $D' EXIT +make -s +RUN=${RUNNER:-} +# a case that hangs fails rather than holding the job +command -v timeout >/dev/null 2>&1 && RUN="timeout 120 $RUN" +EXE=${EXE:-} +head -c 1048579 /dev/urandom > $D/body.bin +python3 -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" $D/body.bin > $D/body.sha256 +openssl req -x509 -newkey rsa:2048 -nodes -keyout $D/ca.key -out $D/ca.pem -days 2 -subj "/CN=test CA" 2>/dev/null +openssl req -newkey rsa:2048 -nodes -keyout $D/srv.key -out $D/srv.csr -subj "/CN=localhost" 2>/dev/null +printf 'subjectAltName=DNS:localhost\nbasicConstraints=CA:FALSE\n' > $D/srv.ext +openssl x509 -req -in $D/srv.csr -CA $D/ca.pem -CAkey $D/ca.key -CAcreateserial -out $D/srv.pem -days 2 -extfile $D/srv.ext 2>/dev/null +openssl x509 -in $D/ca.pem -outform DER -out $D/ca.der +openssl req -x509 -newkey rsa:2048 -nodes -keyout $D/cca.key -out $D/cca.pem -days 2 -subj "/CN=client CA" 2>/dev/null +openssl req -newkey rsa:2048 -nodes -keyout $D/cli.key -out $D/cli.csr -subj "/CN=client" 2>/dev/null +openssl x509 -req -in $D/cli.csr -CA $D/cca.pem -CAkey $D/cca.key -CAcreateserial -out $D/cli.pem -days 2 2>/dev/null +openssl x509 -in $D/cli.pem -outform DER -out $D/cli.der +openssl rsa -in $D/cli.key -traditional -outform DER -out $D/cli.key.der 2>/dev/null +HP=${HTTP_PORT:-45180} +TP=${TLS_PORT:-45443} +python3 server.py $HP $D & HTTP=$! +(cd $D && exec openssl s_server -accept $TP -cert srv.pem -key srv.key -tls1_2 -cipher AES256-SHA \ + -Verify 1 -CAfile cca.pem -WWW) >/dev/null 2>&1 & TLS=$! +sleep 1 +fail=0 +for t in get post post-read form 404 timeout multi pause threads restart tls tls-refused; do + if $RUN ./curl_compat_test$EXE $t $HP $TP $D > $D/out 2>&1; then echo "ok: $t" + else echo "FAIL: $t: $(cat $D/out)"; fail=1; fi +done +[ $fail -eq 0 ] && echo "[pass] curl_compat" +exit $fail diff --git a/dependencies/curl-compat/test/server.py b/dependencies/curl-compat/test/server.py new file mode 100644 index 00000000..1ac9a56b --- /dev/null +++ b/dependencies/curl-compat/test/server.py @@ -0,0 +1,30 @@ +# The test server: a file, an echo of a POST's hash, a 404 and a request +# that is never answered. +import hashlib, http.server, os, sys, time +port, root = int(sys.argv[1]), sys.argv[2] +class H(http.server.BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + def log_message(self, *a): pass + def reply(self, code, body, ctype="application/octet-stream"): + self.send_response(code) + self.send_header("Content-Type", ctype) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + def do_GET(self): + if self.path == "/body.bin": + with open(os.path.join(root, "body.bin"), "rb") as f: + self.reply(200, f.read()) + elif self.path == "/stall": + time.sleep(30) + else: + self.reply(404, b"not here", "text/plain") + def do_POST(self): + n = int(self.headers.get("Content-Length", "0")) + data = self.rfile.read(n) + out = "%s %s %s" % (hashlib.sha256(data).hexdigest(), self.headers.get("X-Test"), self.headers.get("Content-Type")) + self.reply(200, out.encode(), "text/plain") +class S(http.server.ThreadingHTTPServer): + allow_reuse_address = True + daemon_threads = True +S(("127.0.0.1", port), H).serve_forever() diff --git a/dependencies/fmt b/dependencies/fmt new file mode 160000 index 00000000..407c905e --- /dev/null +++ b/dependencies/fmt @@ -0,0 +1 @@ +Subproject commit 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f diff --git a/dependencies/glm b/dependencies/glm new file mode 160000 index 00000000..0af55cce --- /dev/null +++ b/dependencies/glm @@ -0,0 +1 @@ +Subproject commit 0af55ccecd98d4e5a8d1fad7de25ba429d60e863 diff --git a/dependencies/glslang b/dependencies/glslang new file mode 160000 index 00000000..1062752a --- /dev/null +++ b/dependencies/glslang @@ -0,0 +1 @@ +Subproject commit 1062752a891c95b2bfeed9e356562d88f9df84ac diff --git a/dependencies/ih264d/CMakeLists.txt b/dependencies/ih264d/CMakeLists.txt deleted file mode 100644 index a8ef3776..00000000 --- a/dependencies/ih264d/CMakeLists.txt +++ /dev/null @@ -1,204 +0,0 @@ -cmake_minimum_required (VERSION 3.8) - -project ("ih264d") - -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" -"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" -) - -if (CMAKE_OSX_ARCHITECTURES) -set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) -else() -set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) -endif() - -if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64") -set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86") -include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES}) -target_sources(ih264d PRIVATE -"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/x86/ih264d_function_selector.c" -"decoder/x86/ih264d_function_selector_sse42.c" -"decoder/x86/ih264d_function_selector_ssse3.c" -) -elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64") -enable_language( C CXX ASM ) -set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm") -include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES}) -target_sources(ih264d PRIVATE -"common/armv8/ih264_deblk_chroma_av8.s" -"common/armv8/ih264_deblk_luma_av8.s" -"common/armv8/ih264_default_weighted_pred_av8.s" -"common/armv8/ih264_ihadamard_scaling_av8.s" -"common/armv8/ih264_inter_pred_chroma_av8.s" -"common/armv8/ih264_inter_pred_filters_luma_horz_av8.s" -"common/armv8/ih264_inter_pred_filters_luma_vert_av8.s" -"common/armv8/ih264_inter_pred_luma_copy_av8.s" -"common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s" -"common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s" -"common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s" -"common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s" -"common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s" -"common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s" -"common/armv8/ih264_intra_pred_chroma_av8.s" -"common/armv8/ih264_intra_pred_luma_16x16_av8.s" -"common/armv8/ih264_intra_pred_luma_4x4_av8.s" -"common/armv8/ih264_intra_pred_luma_8x8_av8.s" -"common/armv8/ih264_iquant_itrans_recon_av8.s" -"common/armv8/ih264_iquant_itrans_recon_dc_av8.s" -"common/armv8/ih264_mem_fns_neon_av8.s" -"common/armv8/ih264_neon_macros.s" -"common/armv8/ih264_padding_neon_av8.s" -"common/armv8/ih264_platform_macros.h" -"common/armv8/ih264_resi_trans_quant_av8.s" -"common/armv8/ih264_weighted_bi_pred_av8.s" -"common/armv8/ih264_weighted_pred_av8.s" -"decoder/arm/ih264d_function_selector_a9q.c" -"decoder/arm/ih264d_function_selector_av8.c" -"decoder/arm/ih264d_function_selector.c" -) -target_compile_options(ih264d PRIVATE -DARMV8 $<$:-Wno-unused-command-line-argument>) -if(NOT MSVC) - set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") -endif() -if(APPLE) - target_sources(ih264d PRIVATE "common/armv8/macos_arm_symbol_aliases.s") -endif() -else() -message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}") -endif() - -if(MSVC) -set_property(TARGET ih264d PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - -# tune settings for slightly better performance -target_compile_options(ih264d PRIVATE $<$:/Oi>) # enable intrinsic functions -target_compile_options(ih264d PRIVATE $<$:/Ot>) # favor speed -target_compile_options(ih264d PRIVATE "/GS-") # disable runtime checks - -endif() diff --git a/dependencies/ih264d/CMakeSettings.json b/dependencies/ih264d/CMakeSettings.json deleted file mode 100644 index d2bcfced..00000000 --- a/dependencies/ih264d/CMakeSettings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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/libretro-common/compat/compat_posix_string.c b/dependencies/libretro-common/compat/compat_posix_string.c new file mode 100644 index 00000000..b0ea26a4 --- /dev/null +++ b/dependencies/libretro-common/compat/compat_posix_string.c @@ -0,0 +1,117 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (compat_posix_string.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include + +#include + +#ifdef _WIN32 + +#undef strcasecmp +#undef strdup +#undef isblank +#undef strtok_r +#include +#include +#include +#include + +#include + +/* ASCII case folding, not tolower. + * + * tolower takes an int that must be representable as unsigned char or + * EOF. A plain char sign-extends any byte above 0x7F into a negative + * value, so passing one straight in is undefined - and these are + * compared on file paths and user text, where such bytes are ordinary. + * glibc happens to tolerate it because its table is offset for exactly + * this mistake; nothing guarantees the next libc will. + * + * Folding only A-Z is also what the callers mean. Anything wider makes + * a comparison depend on the user's locale, which for extensions, + * driver names and identifiers is not wanted. */ +#define COMPAT_LOWER(c) \ + (((c) >= 'A' && (c) <= 'Z') ? ((c) + ('a' - 'A')) : (c)) + +int retro_strcasecmp__(const char *a, const char *b) +{ + while (*a && *b) + { + int a_ = COMPAT_LOWER((unsigned char)*a); + int b_ = COMPAT_LOWER((unsigned char)*b); + + if (a_ != b_) + return a_ - b_; + + a++; + b++; + } + + return COMPAT_LOWER((unsigned char)*a) - COMPAT_LOWER((unsigned char)*b); +} + +char *retro_strdup__(const char *orig) +{ + size_t _len = strlen(orig) + 1; + char *ret = (char*)malloc(_len); + if (!ret) + return NULL; + memcpy(ret, orig, _len); + return ret; +} + +int retro_isblank__(int c) +{ + return (c == ' ') || (c == '\t'); +} + +char *retro_strtok_r__(char *str, const char *delim, char **saveptr) +{ + char *first = NULL; + if (!saveptr || !delim) + return NULL; + + if (str) + *saveptr = str; + + do + { + char *ptr = NULL; + first = *saveptr; + while (*first && strchr(delim, *first)) + *first++ = '\0'; + + if (*first == '\0') + return NULL; + + ptr = first + 1; + + while (*ptr && !strchr(delim, *ptr)) + ptr++; + + *saveptr = ptr + (*ptr ? 1 : 0); + *ptr = '\0'; + } while (strlen(first) == 0); + + return first; +} +#endif diff --git a/dependencies/libretro-common/compat/compat_strcasestr.c b/dependencies/libretro-common/compat/compat_strcasestr.c new file mode 100644 index 00000000..ebc9fa98 --- /dev/null +++ b/dependencies/libretro-common/compat/compat_strcasestr.c @@ -0,0 +1,99 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (compat_strcasestr.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include + +#include + +/* ASCII case folding, done here rather than through tolower. + * + * tolower takes an int whose value must be representable as unsigned + * char or EOF; passing a plain char sign-extends anything above 0x7F + * into a negative value, which is undefined. This is called on + * arbitrary bytes - file paths, HTTP responses, config text - so that + * was reachable. Folding only A-Z is also what every caller here + * means: extensions, window manager names, config keys and header + * fields are all ASCII, and locale-dependent folding of anything else + * would make the result depend on the user's locale. */ +#define STRCASESTR_LOWER(c) \ + (((c) >= 'A' && (c) <= 'Z') ? ((c) + ('a' - 'A')) : (c)) + +/* Pretty much strncasecmp. */ +static int casencmp(const char *a, const char *b, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) + { + int a_lower = STRCASESTR_LOWER((unsigned char)a[i]); + int b_lower = STRCASESTR_LOWER((unsigned char)b[i]); + if (a_lower != b_lower) + return a_lower - b_lower; + } + + return 0; +} + +/* Let memchr find the candidate positions. + * + * Comparing at every offset means folding every byte of the haystack, + * which measured some sixty times slower than strstr on the same + * inputs - enough to matter where this is called per file over a + * directory. Only the first character of the needle decides whether + * an offset is worth examining, and memchr finds those far faster than + * a byte loop can, so it does the skipping and the fold only runs + * where a match is actually possible. A needle whose first character + * has no case of its own - a digit, a punctuation mark - costs one + * memchr rather than two. */ +char *compat_strcasestr(const char *haystack, const char *needle) +{ + size_t nlen = strlen(needle); + size_t hlen = strlen(haystack); + const char *end = haystack + hlen; + int lo; + int up; + + if (!nlen) + return (char*)haystack; + if (nlen > hlen) + return NULL; + + lo = STRCASESTR_LOWER((unsigned char)needle[0]); + up = (lo >= 'a' && lo <= 'z') ? (lo - ('a' - 'A')) : lo; + + while ((size_t)(end - haystack) >= nlen) + { + size_t left = (size_t)(end - haystack); + const char *a = (const char*)memchr(haystack, lo, left); + const char *b = (up != lo) + ? (const char*)memchr(haystack, up, left) : NULL; + const char *p = !a ? b : (!b ? a : (a < b ? a : b)); + + if (!p || (size_t)(end - p) < nlen) + return NULL; + if (!casencmp(p + 1, needle + 1, nlen - 1)) + return (char*)p; + haystack = p + 1; + } + + return NULL; +} diff --git a/dependencies/libretro-common/compat/compat_strl.c b/dependencies/libretro-common/compat/compat_strl.c new file mode 100644 index 00000000..879f3067 --- /dev/null +++ b/dependencies/libretro-common/compat/compat_strl.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (compat_strl.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include +#include +#include + +/* Implementation of strlcpy()/strlcat() based on OpenBSD. */ + +#if !(defined(__MACH__) && defined(__APPLE__)) +size_t strlcpy(char *s, const char *in, size_t len) +{ + size_t src_len = strlen(in); +#ifdef LIBRETRO_STRL_CHECK_OVERLAP + /* Test builds: fail the way macOS's fortified libc does on an + * overlapping copy (__chk_fail_overlap), so the Linux test suites + * catch what only Apple's runtime would otherwise catch. */ + if (len && src_len) + { + const char *s_end = s + (src_len < len - 1 ? src_len : len - 1) + 1; + const char *in_end = in + src_len + 1; + if (!(s_end <= in || in_end <= s)) + { + fprintf(stderr, "strlcpy: overlapping copy (dst %p, src %p, %u bytes)\n", + (void*)s, (const void*)in, (unsigned)src_len); + abort(); + } + } +#endif + if (len) + { + size_t cpy_len = src_len < len - 1 ? src_len : len - 1; + memcpy(s, in, cpy_len); + s[cpy_len] = '\0'; + } + return src_len; +} + +/* The destination scan is bounded by 'len': 's' is not required to + * contain a NUL within the first 'len' bytes. When it does not, no + * bytes are written and the return value is len + strlen(source), + * matching OpenBSD, Darwin and glibc. */ +size_t strlcat(char *s, const char *source, size_t len) +{ + const char *nul = (const char*)memchr(s, 0, len); + size_t dst_len = nul ? (size_t)(nul - s) : len; + if (dst_len == len) + return len + strlen(source); + return dst_len + strlcpy(s + dst_len, source, len - dst_len); +} +#endif diff --git a/dependencies/libretro-common/compat/fopen_utf8.c b/dependencies/libretro-common/compat/fopen_utf8.c new file mode 100644 index 00000000..e07edfe8 --- /dev/null +++ b/dependencies/libretro-common/compat/fopen_utf8.c @@ -0,0 +1,149 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (fopen_utf8.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#if defined(__WINRT__) +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +#undef fopen + +#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME) +static void *fopen_utf8_ansi(const char * filename, const char * mode) +{ + char * filename_local = utf8_to_local_string_alloc(filename); + if (filename_local) + { + FILE *ret = fopen(filename_local, mode); + free(filename_local); + return ret; + } + return NULL; +} +#endif + +#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME) +static void *fopen_utf8_wide(const char * filename, const char * mode) +{ + wchar_t * filename_w = utf8_to_utf16_string_alloc(filename); + if (filename_w) + { + FILE *ret = NULL; +#if defined(__WINRT__) + HANDLE file_handle = INVALID_HANDLE_VALUE; + DWORD desired_access = 0; + DWORD creation_disposition = OPEN_EXISTING; + int open_flags = O_BINARY; + int fd = -1; + bool append = mode && strchr(mode, 'a'); + bool write = mode && strchr(mode, 'w'); + bool plus = mode && strchr(mode, '+'); + wchar_t *path = filename_w; + + while (*path) + { + if (*path == L'/') + *path = L'\\'; + path++; + } + + if (mode && strchr(mode, 'r')) + desired_access |= GENERIC_READ; + if (write || append || plus) + desired_access |= GENERIC_WRITE; + if (plus) + desired_access |= GENERIC_READ; + + if (append) + creation_disposition = OPEN_ALWAYS; + else if (write) + creation_disposition = CREATE_ALWAYS; + + if (plus) + open_flags |= O_RDWR; + else if (append || write) + open_flags |= O_WRONLY; + else + open_flags |= O_RDONLY; + + if (append) + open_flags |= O_APPEND; + if (write || append) + open_flags |= O_CREAT; + if (write) + open_flags |= O_TRUNC; + + file_handle = CreateFile2FromAppW(filename_w, desired_access, + FILE_SHARE_READ | FILE_SHARE_WRITE, + creation_disposition, NULL); + if (file_handle != INVALID_HANDLE_VALUE) + { + fd = _open_osfhandle((intptr_t)file_handle, open_flags); + if (fd != -1) + ret = _fdopen(fd, mode); + + if (!ret) + { + if (fd != -1) + _close(fd); + else + CloseHandle(file_handle); + } + } +#else + wchar_t *mode_w = utf8_to_utf16_string_alloc(mode); + if (mode_w) + { + ret = _wfopen(filename_w, mode_w); + free(mode_w); + } +#endif + free(filename_w); + return ret; + } + return NULL; +} +#endif + +void *fopen_utf8(const char * filename, const char * mode) +{ +#if defined(LEGACY_WIN32_RUNTIME) + if (win32_needs_local_encoding()) + return fopen_utf8_ansi(filename, mode); + return fopen_utf8_wide(filename, mode); +#elif defined(LEGACY_WIN32) + return fopen_utf8_ansi(filename, mode); +#else + return fopen_utf8_wide(filename, mode); +#endif +} +#endif diff --git a/dependencies/libretro-common/crypto/crypto.c b/dependencies/libretro-common/crypto/crypto.c new file mode 100644 index 00000000..f1204c3e --- /dev/null +++ b/dependencies/libretro-common/crypto/crypto.c @@ -0,0 +1,2936 @@ +/* Copyright (C) 2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (crypto.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include +#include + +/* AES-NI + PCLMULQDQ path for x86, chosen at run time. Same gating as + * the SHA-NI path in lrc_hash.c: GCC / Clang with the target + * attribute, so the instructions are confined to the functions that + * carry them and the TU's own baseline is untouched. */ +#if (defined(__x86_64__) || defined(__i386__)) && !defined(_MSC_VER) && !defined(AES_NO_X86) +#if defined(__has_attribute) && defined(__has_include) +#if __has_attribute(target) && __has_include() \ + && (!defined(__SCE__) || (defined(__AES__) && defined(__PCLMUL__) && defined(__SSSE3__))) +#define AES_HAVE_X86_PATH 1 +#endif +#endif +#endif + +#if defined(AES_HAVE_X86_PATH) +#include +#include +#define AES_TARGET_X86 __attribute__((target("aes,pclmul,ssse3"))) +#endif + +/* ARMv8 AES + PMULL, same shape as lrc_hash.c's SHA path: used + * unconditionally when the baseline has the crypto extension, else + * compiled in under the target attribute and chosen at run time on + * toolchains whose arm_neon.h declares the intrinsics regardless of + * the baseline (Clang 16+, GCC 9+). */ +#if (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_MSC_VER) && !defined(AES_NO_ARM) +#if defined(__ARM_FEATURE_CRYPTO) || (defined(__ARM_FEATURE_AES) && defined(__ARM_FEATURE_SHA2)) +#define AES_HAVE_ARM_PATH 1 +#elif (defined(__clang__) && __clang_major__ >= 16) \ + || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 9) +#define AES_HAVE_ARM_PATH 1 +#define AES_ARM_NEEDS_TARGET 1 +#define AES_ARM_DISPATCH 1 +#endif +#endif + +#if defined(AES_HAVE_ARM_PATH) +#include +#include +#if defined(AES_ARM_NEEDS_TARGET) +#define AES_TARGET_ARM __attribute__((target("+crypto"))) +#else +#define AES_TARGET_ARM +#endif +#endif + +/* A statement for each of eight, four or one interleaved chains */ +#define AES_CBC_EACH8(S) S(0) S(1) S(2) S(3) S(4) S(5) S(6) S(7) +#define AES_CBC_EACH4(S) S(0) S(1) S(2) S(3) +#define AES_CBC_EACH1(S) S(0) + +int crypto_memeq_ct(const void *a, const void *b, size_t len) +{ + const volatile uint8_t *pa = (const volatile uint8_t*)a; + const volatile uint8_t *pb = (const volatile uint8_t*)b; + uint8_t acc = 0; + size_t i; + + for (i = 0; i < len; i++) + acc |= (uint8_t)(pa[i] ^ pb[i]); + + /* 1 when every octet matched, with no data-dependent branch. */ + return (int)((((unsigned)acc - 1) >> 8) & 1); +} + +void crypto_memzero(void *p, size_t len) +{ + volatile uint8_t *vp = (volatile uint8_t*)p; + while (len--) + *vp++ = 0; +} + +static const uint8_t aes_sbox[256] = { + 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_t aes_rcon[10] = { + 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36 +}; + +#define XTIME(x) ((uint8_t)(((x) << 1) ^ ((((x) >> 7) & 1) * 0x1b))) + +static uint32_t aes_sub_word(uint32_t w) +{ + return ((uint32_t)aes_sbox[(w >> 24) & 0xff] << 24) + | ((uint32_t)aes_sbox[(w >> 16) & 0xff] << 16) + | ((uint32_t)aes_sbox[(w >> 8) & 0xff] << 8) + | (uint32_t)aes_sbox[ w & 0xff]; +} + +/* Round tables: ft[0][x] = (2s, s, s, 3s) with s = sbox[x], the other + * three are its byte rotations. The inverse S-box and imc[x] = (14x, + * 9x, 13x, 11x) serve the inverse cipher, which runs in the standard + * order on the encryption schedule (InvMixColumns on the state, so no + * second schedule is kept). All three are constants, generated from + * aes_sbox above: nothing to build on first use, so nothing for two + * threads setting up keys at once to race on. */ +static const uint32_t aes_ft[4][256] = { + { + 0xc66363a5UL,0xf87c7c84UL,0xee777799UL,0xf67b7b8dUL,0xfff2f20dUL,0xd66b6bbdUL, + 0xde6f6fb1UL,0x91c5c554UL,0x60303050UL,0x02010103UL,0xce6767a9UL,0x562b2b7dUL, + 0xe7fefe19UL,0xb5d7d762UL,0x4dababe6UL,0xec76769aUL,0x8fcaca45UL,0x1f82829dUL, + 0x89c9c940UL,0xfa7d7d87UL,0xeffafa15UL,0xb25959ebUL,0x8e4747c9UL,0xfbf0f00bUL, + 0x41adadecUL,0xb3d4d467UL,0x5fa2a2fdUL,0x45afafeaUL,0x239c9cbfUL,0x53a4a4f7UL, + 0xe4727296UL,0x9bc0c05bUL,0x75b7b7c2UL,0xe1fdfd1cUL,0x3d9393aeUL,0x4c26266aUL, + 0x6c36365aUL,0x7e3f3f41UL,0xf5f7f702UL,0x83cccc4fUL,0x6834345cUL,0x51a5a5f4UL, + 0xd1e5e534UL,0xf9f1f108UL,0xe2717193UL,0xabd8d873UL,0x62313153UL,0x2a15153fUL, + 0x0804040cUL,0x95c7c752UL,0x46232365UL,0x9dc3c35eUL,0x30181828UL,0x379696a1UL, + 0x0a05050fUL,0x2f9a9ab5UL,0x0e070709UL,0x24121236UL,0x1b80809bUL,0xdfe2e23dUL, + 0xcdebeb26UL,0x4e272769UL,0x7fb2b2cdUL,0xea75759fUL,0x1209091bUL,0x1d83839eUL, + 0x582c2c74UL,0x341a1a2eUL,0x361b1b2dUL,0xdc6e6eb2UL,0xb45a5aeeUL,0x5ba0a0fbUL, + 0xa45252f6UL,0x763b3b4dUL,0xb7d6d661UL,0x7db3b3ceUL,0x5229297bUL,0xdde3e33eUL, + 0x5e2f2f71UL,0x13848497UL,0xa65353f5UL,0xb9d1d168UL,0x00000000UL,0xc1eded2cUL, + 0x40202060UL,0xe3fcfc1fUL,0x79b1b1c8UL,0xb65b5bedUL,0xd46a6abeUL,0x8dcbcb46UL, + 0x67bebed9UL,0x7239394bUL,0x944a4adeUL,0x984c4cd4UL,0xb05858e8UL,0x85cfcf4aUL, + 0xbbd0d06bUL,0xc5efef2aUL,0x4faaaae5UL,0xedfbfb16UL,0x864343c5UL,0x9a4d4dd7UL, + 0x66333355UL,0x11858594UL,0x8a4545cfUL,0xe9f9f910UL,0x04020206UL,0xfe7f7f81UL, + 0xa05050f0UL,0x783c3c44UL,0x259f9fbaUL,0x4ba8a8e3UL,0xa25151f3UL,0x5da3a3feUL, + 0x804040c0UL,0x058f8f8aUL,0x3f9292adUL,0x219d9dbcUL,0x70383848UL,0xf1f5f504UL, + 0x63bcbcdfUL,0x77b6b6c1UL,0xafdada75UL,0x42212163UL,0x20101030UL,0xe5ffff1aUL, + 0xfdf3f30eUL,0xbfd2d26dUL,0x81cdcd4cUL,0x180c0c14UL,0x26131335UL,0xc3ecec2fUL, + 0xbe5f5fe1UL,0x359797a2UL,0x884444ccUL,0x2e171739UL,0x93c4c457UL,0x55a7a7f2UL, + 0xfc7e7e82UL,0x7a3d3d47UL,0xc86464acUL,0xba5d5de7UL,0x3219192bUL,0xe6737395UL, + 0xc06060a0UL,0x19818198UL,0x9e4f4fd1UL,0xa3dcdc7fUL,0x44222266UL,0x542a2a7eUL, + 0x3b9090abUL,0x0b888883UL,0x8c4646caUL,0xc7eeee29UL,0x6bb8b8d3UL,0x2814143cUL, + 0xa7dede79UL,0xbc5e5ee2UL,0x160b0b1dUL,0xaddbdb76UL,0xdbe0e03bUL,0x64323256UL, + 0x743a3a4eUL,0x140a0a1eUL,0x924949dbUL,0x0c06060aUL,0x4824246cUL,0xb85c5ce4UL, + 0x9fc2c25dUL,0xbdd3d36eUL,0x43acacefUL,0xc46262a6UL,0x399191a8UL,0x319595a4UL, + 0xd3e4e437UL,0xf279798bUL,0xd5e7e732UL,0x8bc8c843UL,0x6e373759UL,0xda6d6db7UL, + 0x018d8d8cUL,0xb1d5d564UL,0x9c4e4ed2UL,0x49a9a9e0UL,0xd86c6cb4UL,0xac5656faUL, + 0xf3f4f407UL,0xcfeaea25UL,0xca6565afUL,0xf47a7a8eUL,0x47aeaee9UL,0x10080818UL, + 0x6fbabad5UL,0xf0787888UL,0x4a25256fUL,0x5c2e2e72UL,0x381c1c24UL,0x57a6a6f1UL, + 0x73b4b4c7UL,0x97c6c651UL,0xcbe8e823UL,0xa1dddd7cUL,0xe874749cUL,0x3e1f1f21UL, + 0x964b4bddUL,0x61bdbddcUL,0x0d8b8b86UL,0x0f8a8a85UL,0xe0707090UL,0x7c3e3e42UL, + 0x71b5b5c4UL,0xcc6666aaUL,0x904848d8UL,0x06030305UL,0xf7f6f601UL,0x1c0e0e12UL, + 0xc26161a3UL,0x6a35355fUL,0xae5757f9UL,0x69b9b9d0UL,0x17868691UL,0x99c1c158UL, + 0x3a1d1d27UL,0x279e9eb9UL,0xd9e1e138UL,0xebf8f813UL,0x2b9898b3UL,0x22111133UL, + 0xd26969bbUL,0xa9d9d970UL,0x078e8e89UL,0x339494a7UL,0x2d9b9bb6UL,0x3c1e1e22UL, + 0x15878792UL,0xc9e9e920UL,0x87cece49UL,0xaa5555ffUL,0x50282878UL,0xa5dfdf7aUL, + 0x038c8c8fUL,0x59a1a1f8UL,0x09898980UL,0x1a0d0d17UL,0x65bfbfdaUL,0xd7e6e631UL, + 0x844242c6UL,0xd06868b8UL,0x824141c3UL,0x299999b0UL,0x5a2d2d77UL,0x1e0f0f11UL, + 0x7bb0b0cbUL,0xa85454fcUL,0x6dbbbbd6UL,0x2c16163aUL + }, + { + 0xa5c66363UL,0x84f87c7cUL,0x99ee7777UL,0x8df67b7bUL,0x0dfff2f2UL,0xbdd66b6bUL, + 0xb1de6f6fUL,0x5491c5c5UL,0x50603030UL,0x03020101UL,0xa9ce6767UL,0x7d562b2bUL, + 0x19e7fefeUL,0x62b5d7d7UL,0xe64dababUL,0x9aec7676UL,0x458fcacaUL,0x9d1f8282UL, + 0x4089c9c9UL,0x87fa7d7dUL,0x15effafaUL,0xebb25959UL,0xc98e4747UL,0x0bfbf0f0UL, + 0xec41adadUL,0x67b3d4d4UL,0xfd5fa2a2UL,0xea45afafUL,0xbf239c9cUL,0xf753a4a4UL, + 0x96e47272UL,0x5b9bc0c0UL,0xc275b7b7UL,0x1ce1fdfdUL,0xae3d9393UL,0x6a4c2626UL, + 0x5a6c3636UL,0x417e3f3fUL,0x02f5f7f7UL,0x4f83ccccUL,0x5c683434UL,0xf451a5a5UL, + 0x34d1e5e5UL,0x08f9f1f1UL,0x93e27171UL,0x73abd8d8UL,0x53623131UL,0x3f2a1515UL, + 0x0c080404UL,0x5295c7c7UL,0x65462323UL,0x5e9dc3c3UL,0x28301818UL,0xa1379696UL, + 0x0f0a0505UL,0xb52f9a9aUL,0x090e0707UL,0x36241212UL,0x9b1b8080UL,0x3ddfe2e2UL, + 0x26cdebebUL,0x694e2727UL,0xcd7fb2b2UL,0x9fea7575UL,0x1b120909UL,0x9e1d8383UL, + 0x74582c2cUL,0x2e341a1aUL,0x2d361b1bUL,0xb2dc6e6eUL,0xeeb45a5aUL,0xfb5ba0a0UL, + 0xf6a45252UL,0x4d763b3bUL,0x61b7d6d6UL,0xce7db3b3UL,0x7b522929UL,0x3edde3e3UL, + 0x715e2f2fUL,0x97138484UL,0xf5a65353UL,0x68b9d1d1UL,0x00000000UL,0x2cc1ededUL, + 0x60402020UL,0x1fe3fcfcUL,0xc879b1b1UL,0xedb65b5bUL,0xbed46a6aUL,0x468dcbcbUL, + 0xd967bebeUL,0x4b723939UL,0xde944a4aUL,0xd4984c4cUL,0xe8b05858UL,0x4a85cfcfUL, + 0x6bbbd0d0UL,0x2ac5efefUL,0xe54faaaaUL,0x16edfbfbUL,0xc5864343UL,0xd79a4d4dUL, + 0x55663333UL,0x94118585UL,0xcf8a4545UL,0x10e9f9f9UL,0x06040202UL,0x81fe7f7fUL, + 0xf0a05050UL,0x44783c3cUL,0xba259f9fUL,0xe34ba8a8UL,0xf3a25151UL,0xfe5da3a3UL, + 0xc0804040UL,0x8a058f8fUL,0xad3f9292UL,0xbc219d9dUL,0x48703838UL,0x04f1f5f5UL, + 0xdf63bcbcUL,0xc177b6b6UL,0x75afdadaUL,0x63422121UL,0x30201010UL,0x1ae5ffffUL, + 0x0efdf3f3UL,0x6dbfd2d2UL,0x4c81cdcdUL,0x14180c0cUL,0x35261313UL,0x2fc3ececUL, + 0xe1be5f5fUL,0xa2359797UL,0xcc884444UL,0x392e1717UL,0x5793c4c4UL,0xf255a7a7UL, + 0x82fc7e7eUL,0x477a3d3dUL,0xacc86464UL,0xe7ba5d5dUL,0x2b321919UL,0x95e67373UL, + 0xa0c06060UL,0x98198181UL,0xd19e4f4fUL,0x7fa3dcdcUL,0x66442222UL,0x7e542a2aUL, + 0xab3b9090UL,0x830b8888UL,0xca8c4646UL,0x29c7eeeeUL,0xd36bb8b8UL,0x3c281414UL, + 0x79a7dedeUL,0xe2bc5e5eUL,0x1d160b0bUL,0x76addbdbUL,0x3bdbe0e0UL,0x56643232UL, + 0x4e743a3aUL,0x1e140a0aUL,0xdb924949UL,0x0a0c0606UL,0x6c482424UL,0xe4b85c5cUL, + 0x5d9fc2c2UL,0x6ebdd3d3UL,0xef43acacUL,0xa6c46262UL,0xa8399191UL,0xa4319595UL, + 0x37d3e4e4UL,0x8bf27979UL,0x32d5e7e7UL,0x438bc8c8UL,0x596e3737UL,0xb7da6d6dUL, + 0x8c018d8dUL,0x64b1d5d5UL,0xd29c4e4eUL,0xe049a9a9UL,0xb4d86c6cUL,0xfaac5656UL, + 0x07f3f4f4UL,0x25cfeaeaUL,0xafca6565UL,0x8ef47a7aUL,0xe947aeaeUL,0x18100808UL, + 0xd56fbabaUL,0x88f07878UL,0x6f4a2525UL,0x725c2e2eUL,0x24381c1cUL,0xf157a6a6UL, + 0xc773b4b4UL,0x5197c6c6UL,0x23cbe8e8UL,0x7ca1ddddUL,0x9ce87474UL,0x213e1f1fUL, + 0xdd964b4bUL,0xdc61bdbdUL,0x860d8b8bUL,0x850f8a8aUL,0x90e07070UL,0x427c3e3eUL, + 0xc471b5b5UL,0xaacc6666UL,0xd8904848UL,0x05060303UL,0x01f7f6f6UL,0x121c0e0eUL, + 0xa3c26161UL,0x5f6a3535UL,0xf9ae5757UL,0xd069b9b9UL,0x91178686UL,0x5899c1c1UL, + 0x273a1d1dUL,0xb9279e9eUL,0x38d9e1e1UL,0x13ebf8f8UL,0xb32b9898UL,0x33221111UL, + 0xbbd26969UL,0x70a9d9d9UL,0x89078e8eUL,0xa7339494UL,0xb62d9b9bUL,0x223c1e1eUL, + 0x92158787UL,0x20c9e9e9UL,0x4987ceceUL,0xffaa5555UL,0x78502828UL,0x7aa5dfdfUL, + 0x8f038c8cUL,0xf859a1a1UL,0x80098989UL,0x171a0d0dUL,0xda65bfbfUL,0x31d7e6e6UL, + 0xc6844242UL,0xb8d06868UL,0xc3824141UL,0xb0299999UL,0x775a2d2dUL,0x111e0f0fUL, + 0xcb7bb0b0UL,0xfca85454UL,0xd66dbbbbUL,0x3a2c1616UL + }, + { + 0x63a5c663UL,0x7c84f87cUL,0x7799ee77UL,0x7b8df67bUL,0xf20dfff2UL,0x6bbdd66bUL, + 0x6fb1de6fUL,0xc55491c5UL,0x30506030UL,0x01030201UL,0x67a9ce67UL,0x2b7d562bUL, + 0xfe19e7feUL,0xd762b5d7UL,0xabe64dabUL,0x769aec76UL,0xca458fcaUL,0x829d1f82UL, + 0xc94089c9UL,0x7d87fa7dUL,0xfa15effaUL,0x59ebb259UL,0x47c98e47UL,0xf00bfbf0UL, + 0xadec41adUL,0xd467b3d4UL,0xa2fd5fa2UL,0xafea45afUL,0x9cbf239cUL,0xa4f753a4UL, + 0x7296e472UL,0xc05b9bc0UL,0xb7c275b7UL,0xfd1ce1fdUL,0x93ae3d93UL,0x266a4c26UL, + 0x365a6c36UL,0x3f417e3fUL,0xf702f5f7UL,0xcc4f83ccUL,0x345c6834UL,0xa5f451a5UL, + 0xe534d1e5UL,0xf108f9f1UL,0x7193e271UL,0xd873abd8UL,0x31536231UL,0x153f2a15UL, + 0x040c0804UL,0xc75295c7UL,0x23654623UL,0xc35e9dc3UL,0x18283018UL,0x96a13796UL, + 0x050f0a05UL,0x9ab52f9aUL,0x07090e07UL,0x12362412UL,0x809b1b80UL,0xe23ddfe2UL, + 0xeb26cdebUL,0x27694e27UL,0xb2cd7fb2UL,0x759fea75UL,0x091b1209UL,0x839e1d83UL, + 0x2c74582cUL,0x1a2e341aUL,0x1b2d361bUL,0x6eb2dc6eUL,0x5aeeb45aUL,0xa0fb5ba0UL, + 0x52f6a452UL,0x3b4d763bUL,0xd661b7d6UL,0xb3ce7db3UL,0x297b5229UL,0xe33edde3UL, + 0x2f715e2fUL,0x84971384UL,0x53f5a653UL,0xd168b9d1UL,0x00000000UL,0xed2cc1edUL, + 0x20604020UL,0xfc1fe3fcUL,0xb1c879b1UL,0x5bedb65bUL,0x6abed46aUL,0xcb468dcbUL, + 0xbed967beUL,0x394b7239UL,0x4ade944aUL,0x4cd4984cUL,0x58e8b058UL,0xcf4a85cfUL, + 0xd06bbbd0UL,0xef2ac5efUL,0xaae54faaUL,0xfb16edfbUL,0x43c58643UL,0x4dd79a4dUL, + 0x33556633UL,0x85941185UL,0x45cf8a45UL,0xf910e9f9UL,0x02060402UL,0x7f81fe7fUL, + 0x50f0a050UL,0x3c44783cUL,0x9fba259fUL,0xa8e34ba8UL,0x51f3a251UL,0xa3fe5da3UL, + 0x40c08040UL,0x8f8a058fUL,0x92ad3f92UL,0x9dbc219dUL,0x38487038UL,0xf504f1f5UL, + 0xbcdf63bcUL,0xb6c177b6UL,0xda75afdaUL,0x21634221UL,0x10302010UL,0xff1ae5ffUL, + 0xf30efdf3UL,0xd26dbfd2UL,0xcd4c81cdUL,0x0c14180cUL,0x13352613UL,0xec2fc3ecUL, + 0x5fe1be5fUL,0x97a23597UL,0x44cc8844UL,0x17392e17UL,0xc45793c4UL,0xa7f255a7UL, + 0x7e82fc7eUL,0x3d477a3dUL,0x64acc864UL,0x5de7ba5dUL,0x192b3219UL,0x7395e673UL, + 0x60a0c060UL,0x81981981UL,0x4fd19e4fUL,0xdc7fa3dcUL,0x22664422UL,0x2a7e542aUL, + 0x90ab3b90UL,0x88830b88UL,0x46ca8c46UL,0xee29c7eeUL,0xb8d36bb8UL,0x143c2814UL, + 0xde79a7deUL,0x5ee2bc5eUL,0x0b1d160bUL,0xdb76addbUL,0xe03bdbe0UL,0x32566432UL, + 0x3a4e743aUL,0x0a1e140aUL,0x49db9249UL,0x060a0c06UL,0x246c4824UL,0x5ce4b85cUL, + 0xc25d9fc2UL,0xd36ebdd3UL,0xacef43acUL,0x62a6c462UL,0x91a83991UL,0x95a43195UL, + 0xe437d3e4UL,0x798bf279UL,0xe732d5e7UL,0xc8438bc8UL,0x37596e37UL,0x6db7da6dUL, + 0x8d8c018dUL,0xd564b1d5UL,0x4ed29c4eUL,0xa9e049a9UL,0x6cb4d86cUL,0x56faac56UL, + 0xf407f3f4UL,0xea25cfeaUL,0x65afca65UL,0x7a8ef47aUL,0xaee947aeUL,0x08181008UL, + 0xbad56fbaUL,0x7888f078UL,0x256f4a25UL,0x2e725c2eUL,0x1c24381cUL,0xa6f157a6UL, + 0xb4c773b4UL,0xc65197c6UL,0xe823cbe8UL,0xdd7ca1ddUL,0x749ce874UL,0x1f213e1fUL, + 0x4bdd964bUL,0xbddc61bdUL,0x8b860d8bUL,0x8a850f8aUL,0x7090e070UL,0x3e427c3eUL, + 0xb5c471b5UL,0x66aacc66UL,0x48d89048UL,0x03050603UL,0xf601f7f6UL,0x0e121c0eUL, + 0x61a3c261UL,0x355f6a35UL,0x57f9ae57UL,0xb9d069b9UL,0x86911786UL,0xc15899c1UL, + 0x1d273a1dUL,0x9eb9279eUL,0xe138d9e1UL,0xf813ebf8UL,0x98b32b98UL,0x11332211UL, + 0x69bbd269UL,0xd970a9d9UL,0x8e89078eUL,0x94a73394UL,0x9bb62d9bUL,0x1e223c1eUL, + 0x87921587UL,0xe920c9e9UL,0xce4987ceUL,0x55ffaa55UL,0x28785028UL,0xdf7aa5dfUL, + 0x8c8f038cUL,0xa1f859a1UL,0x89800989UL,0x0d171a0dUL,0xbfda65bfUL,0xe631d7e6UL, + 0x42c68442UL,0x68b8d068UL,0x41c38241UL,0x99b02999UL,0x2d775a2dUL,0x0f111e0fUL, + 0xb0cb7bb0UL,0x54fca854UL,0xbbd66dbbUL,0x163a2c16UL + }, + { + 0x6363a5c6UL,0x7c7c84f8UL,0x777799eeUL,0x7b7b8df6UL,0xf2f20dffUL,0x6b6bbdd6UL, + 0x6f6fb1deUL,0xc5c55491UL,0x30305060UL,0x01010302UL,0x6767a9ceUL,0x2b2b7d56UL, + 0xfefe19e7UL,0xd7d762b5UL,0xababe64dUL,0x76769aecUL,0xcaca458fUL,0x82829d1fUL, + 0xc9c94089UL,0x7d7d87faUL,0xfafa15efUL,0x5959ebb2UL,0x4747c98eUL,0xf0f00bfbUL, + 0xadadec41UL,0xd4d467b3UL,0xa2a2fd5fUL,0xafafea45UL,0x9c9cbf23UL,0xa4a4f753UL, + 0x727296e4UL,0xc0c05b9bUL,0xb7b7c275UL,0xfdfd1ce1UL,0x9393ae3dUL,0x26266a4cUL, + 0x36365a6cUL,0x3f3f417eUL,0xf7f702f5UL,0xcccc4f83UL,0x34345c68UL,0xa5a5f451UL, + 0xe5e534d1UL,0xf1f108f9UL,0x717193e2UL,0xd8d873abUL,0x31315362UL,0x15153f2aUL, + 0x04040c08UL,0xc7c75295UL,0x23236546UL,0xc3c35e9dUL,0x18182830UL,0x9696a137UL, + 0x05050f0aUL,0x9a9ab52fUL,0x0707090eUL,0x12123624UL,0x80809b1bUL,0xe2e23ddfUL, + 0xebeb26cdUL,0x2727694eUL,0xb2b2cd7fUL,0x75759feaUL,0x09091b12UL,0x83839e1dUL, + 0x2c2c7458UL,0x1a1a2e34UL,0x1b1b2d36UL,0x6e6eb2dcUL,0x5a5aeeb4UL,0xa0a0fb5bUL, + 0x5252f6a4UL,0x3b3b4d76UL,0xd6d661b7UL,0xb3b3ce7dUL,0x29297b52UL,0xe3e33eddUL, + 0x2f2f715eUL,0x84849713UL,0x5353f5a6UL,0xd1d168b9UL,0x00000000UL,0xeded2cc1UL, + 0x20206040UL,0xfcfc1fe3UL,0xb1b1c879UL,0x5b5bedb6UL,0x6a6abed4UL,0xcbcb468dUL, + 0xbebed967UL,0x39394b72UL,0x4a4ade94UL,0x4c4cd498UL,0x5858e8b0UL,0xcfcf4a85UL, + 0xd0d06bbbUL,0xefef2ac5UL,0xaaaae54fUL,0xfbfb16edUL,0x4343c586UL,0x4d4dd79aUL, + 0x33335566UL,0x85859411UL,0x4545cf8aUL,0xf9f910e9UL,0x02020604UL,0x7f7f81feUL, + 0x5050f0a0UL,0x3c3c4478UL,0x9f9fba25UL,0xa8a8e34bUL,0x5151f3a2UL,0xa3a3fe5dUL, + 0x4040c080UL,0x8f8f8a05UL,0x9292ad3fUL,0x9d9dbc21UL,0x38384870UL,0xf5f504f1UL, + 0xbcbcdf63UL,0xb6b6c177UL,0xdada75afUL,0x21216342UL,0x10103020UL,0xffff1ae5UL, + 0xf3f30efdUL,0xd2d26dbfUL,0xcdcd4c81UL,0x0c0c1418UL,0x13133526UL,0xecec2fc3UL, + 0x5f5fe1beUL,0x9797a235UL,0x4444cc88UL,0x1717392eUL,0xc4c45793UL,0xa7a7f255UL, + 0x7e7e82fcUL,0x3d3d477aUL,0x6464acc8UL,0x5d5de7baUL,0x19192b32UL,0x737395e6UL, + 0x6060a0c0UL,0x81819819UL,0x4f4fd19eUL,0xdcdc7fa3UL,0x22226644UL,0x2a2a7e54UL, + 0x9090ab3bUL,0x8888830bUL,0x4646ca8cUL,0xeeee29c7UL,0xb8b8d36bUL,0x14143c28UL, + 0xdede79a7UL,0x5e5ee2bcUL,0x0b0b1d16UL,0xdbdb76adUL,0xe0e03bdbUL,0x32325664UL, + 0x3a3a4e74UL,0x0a0a1e14UL,0x4949db92UL,0x06060a0cUL,0x24246c48UL,0x5c5ce4b8UL, + 0xc2c25d9fUL,0xd3d36ebdUL,0xacacef43UL,0x6262a6c4UL,0x9191a839UL,0x9595a431UL, + 0xe4e437d3UL,0x79798bf2UL,0xe7e732d5UL,0xc8c8438bUL,0x3737596eUL,0x6d6db7daUL, + 0x8d8d8c01UL,0xd5d564b1UL,0x4e4ed29cUL,0xa9a9e049UL,0x6c6cb4d8UL,0x5656faacUL, + 0xf4f407f3UL,0xeaea25cfUL,0x6565afcaUL,0x7a7a8ef4UL,0xaeaee947UL,0x08081810UL, + 0xbabad56fUL,0x787888f0UL,0x25256f4aUL,0x2e2e725cUL,0x1c1c2438UL,0xa6a6f157UL, + 0xb4b4c773UL,0xc6c65197UL,0xe8e823cbUL,0xdddd7ca1UL,0x74749ce8UL,0x1f1f213eUL, + 0x4b4bdd96UL,0xbdbddc61UL,0x8b8b860dUL,0x8a8a850fUL,0x707090e0UL,0x3e3e427cUL, + 0xb5b5c471UL,0x6666aaccUL,0x4848d890UL,0x03030506UL,0xf6f601f7UL,0x0e0e121cUL, + 0x6161a3c2UL,0x35355f6aUL,0x5757f9aeUL,0xb9b9d069UL,0x86869117UL,0xc1c15899UL, + 0x1d1d273aUL,0x9e9eb927UL,0xe1e138d9UL,0xf8f813ebUL,0x9898b32bUL,0x11113322UL, + 0x6969bbd2UL,0xd9d970a9UL,0x8e8e8907UL,0x9494a733UL,0x9b9bb62dUL,0x1e1e223cUL, + 0x87879215UL,0xe9e920c9UL,0xcece4987UL,0x5555ffaaUL,0x28287850UL,0xdfdf7aa5UL, + 0x8c8c8f03UL,0xa1a1f859UL,0x89898009UL,0x0d0d171aUL,0xbfbfda65UL,0xe6e631d7UL, + 0x4242c684UL,0x6868b8d0UL,0x4141c382UL,0x9999b029UL,0x2d2d775aUL,0x0f0f111eUL, + 0xb0b0cb7bUL,0x5454fca8UL,0xbbbbd66dUL,0x16163a2cUL + } +}; + +static const uint8_t aes_isbox[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 +}; + +static const uint32_t aes_imc[256] = { + 0x00000000UL,0x0e090d0bUL,0x1c121a16UL,0x121b171dUL,0x3824342cUL,0x362d3927UL, + 0x24362e3aUL,0x2a3f2331UL,0x70486858UL,0x7e416553UL,0x6c5a724eUL,0x62537f45UL, + 0x486c5c74UL,0x4665517fUL,0x547e4662UL,0x5a774b69UL,0xe090d0b0UL,0xee99ddbbUL, + 0xfc82caa6UL,0xf28bc7adUL,0xd8b4e49cUL,0xd6bde997UL,0xc4a6fe8aUL,0xcaaff381UL, + 0x90d8b8e8UL,0x9ed1b5e3UL,0x8ccaa2feUL,0x82c3aff5UL,0xa8fc8cc4UL,0xa6f581cfUL, + 0xb4ee96d2UL,0xbae79bd9UL,0xdb3bbb7bUL,0xd532b670UL,0xc729a16dUL,0xc920ac66UL, + 0xe31f8f57UL,0xed16825cUL,0xff0d9541UL,0xf104984aUL,0xab73d323UL,0xa57ade28UL, + 0xb761c935UL,0xb968c43eUL,0x9357e70fUL,0x9d5eea04UL,0x8f45fd19UL,0x814cf012UL, + 0x3bab6bcbUL,0x35a266c0UL,0x27b971ddUL,0x29b07cd6UL,0x038f5fe7UL,0x0d8652ecUL, + 0x1f9d45f1UL,0x119448faUL,0x4be30393UL,0x45ea0e98UL,0x57f11985UL,0x59f8148eUL, + 0x73c737bfUL,0x7dce3ab4UL,0x6fd52da9UL,0x61dc20a2UL,0xad766df6UL,0xa37f60fdUL, + 0xb16477e0UL,0xbf6d7aebUL,0x955259daUL,0x9b5b54d1UL,0x894043ccUL,0x87494ec7UL, + 0xdd3e05aeUL,0xd33708a5UL,0xc12c1fb8UL,0xcf2512b3UL,0xe51a3182UL,0xeb133c89UL, + 0xf9082b94UL,0xf701269fUL,0x4de6bd46UL,0x43efb04dUL,0x51f4a750UL,0x5ffdaa5bUL, + 0x75c2896aUL,0x7bcb8461UL,0x69d0937cUL,0x67d99e77UL,0x3daed51eUL,0x33a7d815UL, + 0x21bccf08UL,0x2fb5c203UL,0x058ae132UL,0x0b83ec39UL,0x1998fb24UL,0x1791f62fUL, + 0x764dd68dUL,0x7844db86UL,0x6a5fcc9bUL,0x6456c190UL,0x4e69e2a1UL,0x4060efaaUL, + 0x527bf8b7UL,0x5c72f5bcUL,0x0605bed5UL,0x080cb3deUL,0x1a17a4c3UL,0x141ea9c8UL, + 0x3e218af9UL,0x302887f2UL,0x223390efUL,0x2c3a9de4UL,0x96dd063dUL,0x98d40b36UL, + 0x8acf1c2bUL,0x84c61120UL,0xaef93211UL,0xa0f03f1aUL,0xb2eb2807UL,0xbce2250cUL, + 0xe6956e65UL,0xe89c636eUL,0xfa877473UL,0xf48e7978UL,0xdeb15a49UL,0xd0b85742UL, + 0xc2a3405fUL,0xccaa4d54UL,0x41ecdaf7UL,0x4fe5d7fcUL,0x5dfec0e1UL,0x53f7cdeaUL, + 0x79c8eedbUL,0x77c1e3d0UL,0x65daf4cdUL,0x6bd3f9c6UL,0x31a4b2afUL,0x3fadbfa4UL, + 0x2db6a8b9UL,0x23bfa5b2UL,0x09808683UL,0x07898b88UL,0x15929c95UL,0x1b9b919eUL, + 0xa17c0a47UL,0xaf75074cUL,0xbd6e1051UL,0xb3671d5aUL,0x99583e6bUL,0x97513360UL, + 0x854a247dUL,0x8b432976UL,0xd134621fUL,0xdf3d6f14UL,0xcd267809UL,0xc32f7502UL, + 0xe9105633UL,0xe7195b38UL,0xf5024c25UL,0xfb0b412eUL,0x9ad7618cUL,0x94de6c87UL, + 0x86c57b9aUL,0x88cc7691UL,0xa2f355a0UL,0xacfa58abUL,0xbee14fb6UL,0xb0e842bdUL, + 0xea9f09d4UL,0xe49604dfUL,0xf68d13c2UL,0xf8841ec9UL,0xd2bb3df8UL,0xdcb230f3UL, + 0xcea927eeUL,0xc0a02ae5UL,0x7a47b13cUL,0x744ebc37UL,0x6655ab2aUL,0x685ca621UL, + 0x42638510UL,0x4c6a881bUL,0x5e719f06UL,0x5078920dUL,0x0a0fd964UL,0x0406d46fUL, + 0x161dc372UL,0x1814ce79UL,0x322bed48UL,0x3c22e043UL,0x2e39f75eUL,0x2030fa55UL, + 0xec9ab701UL,0xe293ba0aUL,0xf088ad17UL,0xfe81a01cUL,0xd4be832dUL,0xdab78e26UL, + 0xc8ac993bUL,0xc6a59430UL,0x9cd2df59UL,0x92dbd252UL,0x80c0c54fUL,0x8ec9c844UL, + 0xa4f6eb75UL,0xaaffe67eUL,0xb8e4f163UL,0xb6edfc68UL,0x0c0a67b1UL,0x02036abaUL, + 0x10187da7UL,0x1e1170acUL,0x342e539dUL,0x3a275e96UL,0x283c498bUL,0x26354480UL, + 0x7c420fe9UL,0x724b02e2UL,0x605015ffUL,0x6e5918f4UL,0x44663bc5UL,0x4a6f36ceUL, + 0x587421d3UL,0x567d2cd8UL,0x37a10c7aUL,0x39a80171UL,0x2bb3166cUL,0x25ba1b67UL, + 0x0f853856UL,0x018c355dUL,0x13972240UL,0x1d9e2f4bUL,0x47e96422UL,0x49e06929UL, + 0x5bfb7e34UL,0x55f2733fUL,0x7fcd500eUL,0x71c45d05UL,0x63df4a18UL,0x6dd64713UL, + 0xd731dccaUL,0xd938d1c1UL,0xcb23c6dcUL,0xc52acbd7UL,0xef15e8e6UL,0xe11ce5edUL, + 0xf307f2f0UL,0xfd0efffbUL,0xa779b492UL,0xa970b999UL,0xbb6bae84UL,0xb562a38fUL, + 0x9f5d80beUL,0x91548db5UL,0x834f9aa8UL,0x8d4697a3UL +}; + +#if defined(AES_HAVE_X86_PATH) +/* 0 not checked yet, 1 no, 2 yes. Threads that check at the same time + * all store the same answer; atomic, so that is not a data race. */ +static retro_atomic_int_t aes_x86_ok; + +static int aes_x86_available(void) +{ + int v = retro_atomic_load_acquire_int(&aes_x86_ok); + if (!v) + { + uint64_t f = cpu_features_get(); + v = (f & RETRO_SIMD_AES) && (f & RETRO_SIMD_PCLMUL) ? 2 : 1; + retro_atomic_store_release_int(&aes_x86_ok, v); + } + return v == 2; +} + +AES_TARGET_X86 +static __m128i aes_x86_bswap32(__m128i v) +{ + const __m128i m = _mm_set_epi8(12,13,14,15, 8,9,10,11, 4,5,6,7, 0,1,2,3); + return _mm_shuffle_epi8(v, m); +} + +AES_TARGET_X86 +static __m128i aes_x86_encrypt(const struct aes_ctx *ctx, __m128i b) +{ + unsigned r; + b = _mm_xor_si128(b, aes_x86_bswap32(_mm_loadu_si128((const __m128i*)ctx->rk))); + for (r = 1; r < ctx->rounds; r++) + b = _mm_aesenc_si128(b, aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r)))); + return _mm_aesenclast_si128(b, aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r)))); +} + +AES_TARGET_X86 +static void aes_x86_encrypt_block(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ + _mm_storeu_si128((__m128i*)out, + aes_x86_encrypt(ctx, _mm_loadu_si128((const __m128i*)in))); +} + +/* Inverse cipher on the encryption schedule: aesdec wants the round + * keys through InvMixColumns, applied here per block; a run of blocks + * goes through aes_x86_cbc_decrypt(), which makes them once. */ +AES_TARGET_X86 +static void aes_x86_decrypt_block(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ + unsigned r = ctx->rounds; + __m128i b = _mm_loadu_si128((const __m128i*)in); + b = _mm_xor_si128(b, aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r)))); + for (r--; r > 0; r--) + b = _mm_aesdec_si128(b, _mm_aesimc_si128( + aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r))))); + b = _mm_aesdeclast_si128(b, aes_x86_bswap32(_mm_loadu_si128((const __m128i*)ctx->rk))); + _mm_storeu_si128((__m128i*)out, b); +} + +/* CBC chains interleaved so the AES unit's latency on each is hidden + * behind the others, the round keys loaded and byte-swapped once per + * call. Eight chains, four or one, each in a register of its own, so + * no round waits on a store and a load besides the cipher. */ + +#define AES_X86_CBC(name, EACH) \ +AES_TARGET_X86 \ +static void name(const __m128i *k, unsigned rounds, uint8_t *const *iv, \ + const uint8_t *const *in, uint8_t *const *out, size_t len) \ +{ \ + __m128i x0, x1, x2, x3, x4, x5, x6, x7; \ + size_t off; \ + unsigned r; \ + EACH(AES_X86_CBC_LOAD) \ + for (off = 0; off < len; off += 16) \ + { \ + EACH(AES_X86_CBC_IN) \ + for (r = 1; r < rounds; r++) \ + { \ + const __m128i kr = k[r]; \ + EACH(AES_X86_CBC_ROUND) \ + } \ + { \ + const __m128i kr = k[rounds]; \ + EACH(AES_X86_CBC_LAST) \ + } \ + } \ + EACH(AES_X86_CBC_STORE) \ + (void)x0; (void)x1; (void)x2; (void)x3; \ + (void)x4; (void)x5; (void)x6; (void)x7; \ +} +#define AES_X86_CBC_LOAD(l) x##l = _mm_loadu_si128((const __m128i*)iv[l]); +#define AES_X86_CBC_IN(l) x##l = _mm_xor_si128(_mm_xor_si128(x##l, \ + _mm_loadu_si128((const __m128i*)(in[l] + off))), k[0]); +#define AES_X86_CBC_ROUND(l) x##l = _mm_aesenc_si128(x##l, kr); +#define AES_X86_CBC_LAST(l) x##l = _mm_aesenclast_si128(x##l, kr); \ + _mm_storeu_si128((__m128i*)(out[l] + off), x##l); +#define AES_X86_CBC_STORE(l) _mm_storeu_si128((__m128i*)iv[l], x##l); + +AES_X86_CBC(aes_x86_cbc8, AES_CBC_EACH8) +AES_X86_CBC(aes_x86_cbc4, AES_CBC_EACH4) +AES_X86_CBC(aes_x86_cbc1, AES_CBC_EACH1) + +AES_TARGET_X86 +static void aes_x86_cbc_lanes(const struct aes_ctx *ctx, unsigned n, + uint8_t *const *iv, const uint8_t *const *in, uint8_t *const *out, + size_t len) +{ + __m128i k[15]; + unsigned r, rounds = ctx->rounds; + + for (r = 0; r <= rounds; r++) + k[r] = aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r))); + for (; n >= 8; n -= 8, iv += 8, in += 8, out += 8) + aes_x86_cbc8(k, rounds, iv, in, out, len); + for (; n >= 4; n -= 4, iv += 4, in += 4, out += 4) + aes_x86_cbc4(k, rounds, iv, in, out, len); + for (; n; n--, iv++, in++, out++) + aes_x86_cbc1(k, rounds, iv, in, out, len); +} + +/* CBC decryption has no chain to wait on: eight blocks go through the + * inverse cipher at once, each XORed with the ciphertext before it. + * The InvMixColumns round keys are made once per call. Every block's + * ciphertext is held in a register before its plaintext is stored, so + * @in and @out may alias. */ +AES_TARGET_X86 +static void aes_x86_cbc_decrypt(const struct aes_ctx *ctx, uint8_t *iv, + const uint8_t *in, uint8_t *out, size_t len) +{ + __m128i k[15]; + __m128i prev = _mm_loadu_si128((const __m128i*)iv); + unsigned r, nr = ctx->rounds; + + k[0] = aes_x86_bswap32(_mm_loadu_si128((const __m128i*)ctx->rk)); + k[nr] = aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * nr))); + for (r = 1; r < nr; r++) + k[r] = _mm_aesimc_si128(aes_x86_bswap32( + _mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r)))); + + while (len >= 128) + { + __m128i c0 = _mm_loadu_si128((const __m128i*)(in)); + __m128i c1 = _mm_loadu_si128((const __m128i*)(in + 16)); + __m128i c2 = _mm_loadu_si128((const __m128i*)(in + 32)); + __m128i c3 = _mm_loadu_si128((const __m128i*)(in + 48)); + __m128i c4 = _mm_loadu_si128((const __m128i*)(in + 64)); + __m128i c5 = _mm_loadu_si128((const __m128i*)(in + 80)); + __m128i c6 = _mm_loadu_si128((const __m128i*)(in + 96)); + __m128i c7 = _mm_loadu_si128((const __m128i*)(in + 112)); + __m128i b0 = _mm_xor_si128(c0, k[nr]); + __m128i b1 = _mm_xor_si128(c1, k[nr]); + __m128i b2 = _mm_xor_si128(c2, k[nr]); + __m128i b3 = _mm_xor_si128(c3, k[nr]); + __m128i b4 = _mm_xor_si128(c4, k[nr]); + __m128i b5 = _mm_xor_si128(c5, k[nr]); + __m128i b6 = _mm_xor_si128(c6, k[nr]); + __m128i b7 = _mm_xor_si128(c7, k[nr]); + for (r = nr - 1; r > 0; r--) + { + __m128i kr = k[r]; + b0 = _mm_aesdec_si128(b0, kr); b1 = _mm_aesdec_si128(b1, kr); + b2 = _mm_aesdec_si128(b2, kr); b3 = _mm_aesdec_si128(b3, kr); + b4 = _mm_aesdec_si128(b4, kr); b5 = _mm_aesdec_si128(b5, kr); + b6 = _mm_aesdec_si128(b6, kr); b7 = _mm_aesdec_si128(b7, kr); + } + _mm_storeu_si128((__m128i*)(out), _mm_xor_si128(_mm_aesdeclast_si128(b0, k[0]), prev)); + _mm_storeu_si128((__m128i*)(out + 16), _mm_xor_si128(_mm_aesdeclast_si128(b1, k[0]), c0)); + _mm_storeu_si128((__m128i*)(out + 32), _mm_xor_si128(_mm_aesdeclast_si128(b2, k[0]), c1)); + _mm_storeu_si128((__m128i*)(out + 48), _mm_xor_si128(_mm_aesdeclast_si128(b3, k[0]), c2)); + _mm_storeu_si128((__m128i*)(out + 64), _mm_xor_si128(_mm_aesdeclast_si128(b4, k[0]), c3)); + _mm_storeu_si128((__m128i*)(out + 80), _mm_xor_si128(_mm_aesdeclast_si128(b5, k[0]), c4)); + _mm_storeu_si128((__m128i*)(out + 96), _mm_xor_si128(_mm_aesdeclast_si128(b6, k[0]), c5)); + _mm_storeu_si128((__m128i*)(out + 112), _mm_xor_si128(_mm_aesdeclast_si128(b7, k[0]), c6)); + prev = c7; + in += 128; + out += 128; + len -= 128; + } + for (; len >= 16; in += 16, out += 16, len -= 16) + { + __m128i c = _mm_loadu_si128((const __m128i*)in); + __m128i b = _mm_xor_si128(c, k[nr]); + for (r = nr - 1; r > 0; r--) + b = _mm_aesdec_si128(b, k[r]); + _mm_storeu_si128((__m128i*)out, + _mm_xor_si128(_mm_aesdeclast_si128(b, k[0]), prev)); + prev = c; + } + _mm_storeu_si128((__m128i*)iv, prev); +} + +/* CTR with eight blocks in flight so the AES unit's latency is hidden, + * the round keys loaded and byte-swapped once per call rather than once + * per round. The counter block is kept byte-reversed, where its 32-bit + * big-endian counter is the low lane: one add steps it (mod 2^32, as + * GCM's inc32) and one shuffle turns it back into a block. */ +AES_TARGET_X86 +static void aes_x86_ctr(const struct aes_ctx *ctx, uint8_t *counter, + const uint8_t *in, uint8_t *out, size_t len) +{ + const __m128i rev = _mm_set_epi8(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); + const __m128i one = _mm_set_epi32(0, 0, 0, 1); + const __m128i two = _mm_set_epi32(0, 0, 0, 2); + const __m128i four = _mm_set_epi32(0, 0, 0, 4); + const __m128i eight = _mm_set_epi32(0, 0, 0, 8); + __m128i ctr = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)counter), rev); + __m128i rk[15]; + unsigned r, nr = ctx->rounds; + + for (r = 0; r <= nr; r++) + rk[r] = aes_x86_bswap32(_mm_loadu_si128((const __m128i*)(ctx->rk + 4 * r))); + + while (len >= 128) + { + __m128i c1 = _mm_add_epi32(ctr, one); + __m128i c2 = _mm_add_epi32(ctr, two); + __m128i c3 = _mm_add_epi32(c1, two); + __m128i c4 = _mm_add_epi32(ctr, four); + __m128i c5 = _mm_add_epi32(c1, four); + __m128i c6 = _mm_add_epi32(c2, four); + __m128i c7 = _mm_add_epi32(c3, four); + __m128i b0 = _mm_xor_si128(_mm_shuffle_epi8(ctr, rev), rk[0]); + __m128i b1 = _mm_xor_si128(_mm_shuffle_epi8(c1, rev), rk[0]); + __m128i b2 = _mm_xor_si128(_mm_shuffle_epi8(c2, rev), rk[0]); + __m128i b3 = _mm_xor_si128(_mm_shuffle_epi8(c3, rev), rk[0]); + __m128i b4 = _mm_xor_si128(_mm_shuffle_epi8(c4, rev), rk[0]); + __m128i b5 = _mm_xor_si128(_mm_shuffle_epi8(c5, rev), rk[0]); + __m128i b6 = _mm_xor_si128(_mm_shuffle_epi8(c6, rev), rk[0]); + __m128i b7 = _mm_xor_si128(_mm_shuffle_epi8(c7, rev), rk[0]); + for (r = 1; r < nr; r++) + { + __m128i k = rk[r]; + b0 = _mm_aesenc_si128(b0, k); b1 = _mm_aesenc_si128(b1, k); + b2 = _mm_aesenc_si128(b2, k); b3 = _mm_aesenc_si128(b3, k); + b4 = _mm_aesenc_si128(b4, k); b5 = _mm_aesenc_si128(b5, k); + b6 = _mm_aesenc_si128(b6, k); b7 = _mm_aesenc_si128(b7, k); + } + _mm_storeu_si128((__m128i*)(out), _mm_xor_si128(_mm_aesenclast_si128(b0, rk[nr]), _mm_loadu_si128((const __m128i*)(in)))); + _mm_storeu_si128((__m128i*)(out + 16), _mm_xor_si128(_mm_aesenclast_si128(b1, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 16)))); + _mm_storeu_si128((__m128i*)(out + 32), _mm_xor_si128(_mm_aesenclast_si128(b2, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 32)))); + _mm_storeu_si128((__m128i*)(out + 48), _mm_xor_si128(_mm_aesenclast_si128(b3, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 48)))); + _mm_storeu_si128((__m128i*)(out + 64), _mm_xor_si128(_mm_aesenclast_si128(b4, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 64)))); + _mm_storeu_si128((__m128i*)(out + 80), _mm_xor_si128(_mm_aesenclast_si128(b5, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 80)))); + _mm_storeu_si128((__m128i*)(out + 96), _mm_xor_si128(_mm_aesenclast_si128(b6, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 96)))); + _mm_storeu_si128((__m128i*)(out + 112), _mm_xor_si128(_mm_aesenclast_si128(b7, rk[nr]), _mm_loadu_si128((const __m128i*)(in + 112)))); + ctr = _mm_add_epi32(ctr, eight); + in += 128; + out += 128; + len -= 128; + } + while (len) + { + size_t n = (len < 16) ? len : 16; + size_t i; + uint8_t ks[16]; + __m128i x = _mm_xor_si128(_mm_shuffle_epi8(ctr, rev), rk[0]); + for (r = 1; r < nr; r++) + x = _mm_aesenc_si128(x, rk[r]); + x = _mm_aesenclast_si128(x, rk[nr]); + if (n == 16) + _mm_storeu_si128((__m128i*)out, _mm_xor_si128(x, + _mm_loadu_si128((const __m128i*)in))); + else + { + _mm_storeu_si128((__m128i*)ks, x); + for (i = 0; i < n; i++) + out[i] = in[i] ^ ks[i]; + } + ctr = _mm_add_epi32(ctr, one); + in += n; + out += n; + len -= n; + } + _mm_storeu_si128((__m128i*)counter, _mm_shuffle_epi8(ctr, rev)); +} + +/* The unreduced 256-bit product of a and b, as *lo / *hi. */ +AES_TARGET_X86 +static void aes_x86_clmul(__m128i a, __m128i b, __m128i *lo, __m128i *hi) +{ + __m128i t3 = _mm_clmulepi64_si128(a, b, 0x00); + __m128i t4 = _mm_clmulepi64_si128(a, b, 0x10); + __m128i t5 = _mm_clmulepi64_si128(a, b, 0x01); + __m128i t6 = _mm_clmulepi64_si128(a, b, 0x11); + t4 = _mm_xor_si128(t4, t5); + *lo = _mm_xor_si128(t3, _mm_slli_si128(t4, 8)); + *hi = _mm_xor_si128(t6, _mm_srli_si128(t4, 8)); +} + +/* The product reduced: one bit left (the reflected order) and modulo + * x^128 + x^7 + x^2 + x + 1. Both steps are linear, so a sum of + * products reduces as one. */ +AES_TARGET_X86 +static __m128i aes_x86_reduce(__m128i t3, __m128i t6) +{ + __m128i t7, t8, t9, t2, t4, t5; + + t7 = _mm_srli_epi32(t3, 31); + t8 = _mm_srli_epi32(t6, 31); + t3 = _mm_slli_epi32(t3, 1); + t6 = _mm_slli_epi32(t6, 1); + t9 = _mm_srli_si128(t7, 12); + t8 = _mm_slli_si128(t8, 4); + t7 = _mm_slli_si128(t7, 4); + t3 = _mm_or_si128(t3, t7); + t6 = _mm_or_si128(t6, t8); + t6 = _mm_or_si128(t6, t9); + + t7 = _mm_slli_epi32(t3, 31); + t8 = _mm_slli_epi32(t3, 30); + t9 = _mm_slli_epi32(t3, 25); + t7 = _mm_xor_si128(t7, t8); + t7 = _mm_xor_si128(t7, t9); + t8 = _mm_srli_si128(t7, 4); + t7 = _mm_slli_si128(t7, 12); + t3 = _mm_xor_si128(t3, t7); + + t2 = _mm_srli_epi32(t3, 1); + t4 = _mm_srli_epi32(t3, 2); + t5 = _mm_srli_epi32(t3, 7); + t2 = _mm_xor_si128(t2, t4); + t2 = _mm_xor_si128(t2, t5); + t2 = _mm_xor_si128(t2, t8); + t3 = _mm_xor_si128(t3, t2); + return _mm_xor_si128(t6, t3); +} + +/* GHASH multiply with PCLMULQDQ, operands byte-reversed so the + * reflected GCM bit order becomes a plain polynomial product + * (Intel's "Algorithm 1"). */ +AES_TARGET_X86 +static __m128i aes_x86_gfmul(__m128i a, __m128i b) +{ + __m128i lo, hi; + aes_x86_clmul(a, b, &lo, &hi); + return aes_x86_reduce(lo, hi); +} + +AES_TARGET_X86 +static void aes_x86_ghash(const struct aes_gcm_ctx *ctx, + uint64_t *y_hi, uint64_t *y_lo, const uint8_t *data, size_t len) +{ + const __m128i rev = _mm_set_epi8(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); + /* h_hi / h_lo are the big-endian halves of the 16 memory octets; + * byte-reversing that whole block is what the multiply wants. */ + __m128i h = _mm_shuffle_epi8(_mm_set_epi64x((long long)__builtin_bswap64(ctx->h_lo), (long long)__builtin_bswap64(ctx->h_hi)), rev); + __m128i y = _mm_shuffle_epi8(_mm_set_epi64x((long long)__builtin_bswap64(*y_lo), (long long)__builtin_bswap64(*y_hi)), rev); + uint8_t tail[16]; + + /* eight blocks per reduction: Y' = (Y+D0)H^8 + D1 H^7 + ... + D7 H, + * each product by Karatsuba (three multiplies, not four), the eight + * summed unreduced - low, high and middle terms apart - and folded + * and reduced once, so the multiplies overlap instead of each + * waiting on the last */ + if (len >= 128) + { + __m128i hp[8], hk[8]; + unsigned j; + hp[0] = h; + for (j = 1; j < 8; j++) + hp[j] = aes_x86_gfmul(hp[j - 1], h); + for (j = 0; j < 8; j++) + hk[j] = _mm_xor_si128(hp[j], _mm_srli_si128(hp[j], 8)); + while (len >= 128) + { + __m128i lo = _mm_setzero_si128(), hi = lo, mid = lo; + for (j = 0; j < 8; j++) + { + __m128i d = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)(data + 16 * j)), rev); + const __m128i p = hp[7 - j], pk = hk[7 - j]; + if (!j) + d = _mm_xor_si128(d, y); + lo = _mm_xor_si128(lo, _mm_clmulepi64_si128(d, p, 0x00)); + hi = _mm_xor_si128(hi, _mm_clmulepi64_si128(d, p, 0x11)); + mid = _mm_xor_si128(mid, _mm_clmulepi64_si128( + _mm_xor_si128(d, _mm_srli_si128(d, 8)), pk, 0x00)); + } + mid = _mm_xor_si128(mid, _mm_xor_si128(lo, hi)); + lo = _mm_xor_si128(lo, _mm_slli_si128(mid, 8)); + hi = _mm_xor_si128(hi, _mm_srli_si128(mid, 8)); + y = aes_x86_reduce(lo, hi); + data += 128; + len -= 128; + } + } + while (len >= 16) + { + __m128i d = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)data), rev); + y = aes_x86_gfmul(_mm_xor_si128(y, d), h); + data += 16; + len -= 16; + } + if (len) + { + memset(tail, 0, 16); + memcpy(tail, data, len); + y = aes_x86_gfmul(_mm_xor_si128(y, _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)tail), rev)), h); + } + + { + uint8_t o[16]; + _mm_storeu_si128((__m128i*)o, _mm_shuffle_epi8(y, rev)); + *y_hi = crypto_load64_be(o); + *y_lo = crypto_load64_be(o + 8); + } +} +#endif + +#if defined(AES_HAVE_ARM_PATH) +static int aes_arm_available(void) +{ +#if defined(AES_ARM_DISPATCH) + static int ok = -1; + if (ok < 0) + ok = (cpu_features_get() & RETRO_SIMD_AES) ? 1 : 0; + return ok; +#else + return 1; +#endif +} + +/* Round key r as the 16 memory octets: the schedule holds big-endian + * words, so a little-endian load needs each word's bytes reversed. */ +AES_TARGET_ARM +static uint8x16_t aes_arm_rk(const struct aes_ctx *ctx, unsigned r) +{ + return vrev32q_u8(vreinterpretq_u8_u32(vld1q_u32(ctx->rk + 4 * r))); +} + +AES_TARGET_ARM +static uint8x16_t aes_arm_encrypt(const struct aes_ctx *ctx, uint8x16_t b) +{ + unsigned r; + for (r = 0; r + 1 < ctx->rounds; r++) + b = vaesmcq_u8(vaeseq_u8(b, aes_arm_rk(ctx, r))); + b = vaeseq_u8(b, aes_arm_rk(ctx, r)); + return veorq_u8(b, aes_arm_rk(ctx, r + 1)); +} + +AES_TARGET_ARM +static void aes_arm_encrypt_block(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ + vst1q_u8(out, aes_arm_encrypt(ctx, vld1q_u8(in))); +} + +/* Equivalent inverse cipher: AESD fuses the round key add with the + * inverse shift and substitution, so the InvMixColumns that the + * standard order puts after the key add moves onto the round key. */ +AES_TARGET_ARM +static void aes_arm_decrypt_block(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ + unsigned r = ctx->rounds; + uint8x16_t b = vaesdq_u8(vld1q_u8(in), aes_arm_rk(ctx, r)); + for (r--; r > 0; r--) + b = vaesdq_u8(vaesimcq_u8(b), vaesimcq_u8(aes_arm_rk(ctx, r))); + vst1q_u8(out, veorq_u8(b, aes_arm_rk(ctx, 0))); +} + +/* CBC chains interleaved, each in a register of its own, as the x86 + * form above; the round keys loaded once. */ +#define AES_ARM_CBC(name, EACH) \ +AES_TARGET_ARM \ +static void name(const uint8x16_t *k, unsigned rounds, uint8_t *const *iv, \ + const uint8_t *const *in, uint8_t *const *out, size_t len) \ +{ \ + uint8x16_t x0, x1, x2, x3, x4, x5, x6, x7; \ + size_t off; \ + unsigned r; \ + EACH(AES_ARM_CBC_LOAD) \ + for (off = 0; off < len; off += 16) \ + { \ + EACH(AES_ARM_CBC_IN) \ + for (r = 0; r + 1 < rounds; r++) \ + { \ + const uint8x16_t kr = k[r]; \ + EACH(AES_ARM_CBC_ROUND) \ + } \ + { \ + const uint8x16_t kr = k[r], kl = k[r + 1]; \ + EACH(AES_ARM_CBC_LAST) \ + } \ + } \ + EACH(AES_ARM_CBC_STORE) \ + (void)x0; (void)x1; (void)x2; (void)x3; \ + (void)x4; (void)x5; (void)x6; (void)x7; \ +} +#define AES_ARM_CBC_LOAD(l) x##l = vld1q_u8(iv[l]); +#define AES_ARM_CBC_IN(l) x##l = veorq_u8(x##l, vld1q_u8(in[l] + off)); +#define AES_ARM_CBC_ROUND(l) x##l = vaesmcq_u8(vaeseq_u8(x##l, kr)); +#define AES_ARM_CBC_LAST(l) x##l = veorq_u8(vaeseq_u8(x##l, kr), kl); \ + vst1q_u8(out[l] + off, x##l); +#define AES_ARM_CBC_STORE(l) vst1q_u8(iv[l], x##l); + +AES_ARM_CBC(aes_arm_cbc8, AES_CBC_EACH8) +AES_ARM_CBC(aes_arm_cbc4, AES_CBC_EACH4) +AES_ARM_CBC(aes_arm_cbc1, AES_CBC_EACH1) + +AES_TARGET_ARM +static void aes_arm_cbc_lanes(const struct aes_ctx *ctx, unsigned n, + uint8_t *const *iv, const uint8_t *const *in, uint8_t *const *out, + size_t len) +{ + uint8x16_t k[15]; + unsigned r, rounds = ctx->rounds; + + for (r = 0; r <= rounds; r++) + k[r] = aes_arm_rk(ctx, r); + for (; n >= 8; n -= 8, iv += 8, in += 8, out += 8) + aes_arm_cbc8(k, rounds, iv, in, out, len); + for (; n >= 4; n -= 4, iv += 4, in += 4, out += 4) + aes_arm_cbc4(k, rounds, iv, in, out, len); + for (; n; n--, iv++, in++, out++) + aes_arm_cbc1(k, rounds, iv, in, out, len); +} + +/* CBC decryption, eight blocks at once as the x86 form above. AESD + * takes the round key before the inverse substitution, so a round is + * AESIMC on the state, then AESD with the round key through AESIMC. */ +AES_TARGET_ARM +static void aes_arm_cbc_decrypt(const struct aes_ctx *ctx, uint8_t *iv, + const uint8_t *in, uint8_t *out, size_t len) +{ + uint8x16_t k[15]; + uint8x16_t prev = vld1q_u8(iv); + unsigned r, nr = ctx->rounds; + + k[0] = aes_arm_rk(ctx, 0); + k[nr] = aes_arm_rk(ctx, nr); + for (r = 1; r < nr; r++) + k[r] = vaesimcq_u8(aes_arm_rk(ctx, r)); + + while (len >= 128) + { + uint8x16_t c0 = vld1q_u8(in); + uint8x16_t c1 = vld1q_u8(in + 16); + uint8x16_t c2 = vld1q_u8(in + 32); + uint8x16_t c3 = vld1q_u8(in + 48); + uint8x16_t c4 = vld1q_u8(in + 64); + uint8x16_t c5 = vld1q_u8(in + 80); + uint8x16_t c6 = vld1q_u8(in + 96); + uint8x16_t c7 = vld1q_u8(in + 112); + uint8x16_t b0 = vaesdq_u8(c0, k[nr]); + uint8x16_t b1 = vaesdq_u8(c1, k[nr]); + uint8x16_t b2 = vaesdq_u8(c2, k[nr]); + uint8x16_t b3 = vaesdq_u8(c3, k[nr]); + uint8x16_t b4 = vaesdq_u8(c4, k[nr]); + uint8x16_t b5 = vaesdq_u8(c5, k[nr]); + uint8x16_t b6 = vaesdq_u8(c6, k[nr]); + uint8x16_t b7 = vaesdq_u8(c7, k[nr]); + for (r = nr - 1; r > 0; r--) + { + const uint8x16_t kr = k[r]; + b0 = vaesdq_u8(vaesimcq_u8(b0), kr); b1 = vaesdq_u8(vaesimcq_u8(b1), kr); + b2 = vaesdq_u8(vaesimcq_u8(b2), kr); b3 = vaesdq_u8(vaesimcq_u8(b3), kr); + b4 = vaesdq_u8(vaesimcq_u8(b4), kr); b5 = vaesdq_u8(vaesimcq_u8(b5), kr); + b6 = vaesdq_u8(vaesimcq_u8(b6), kr); b7 = vaesdq_u8(vaesimcq_u8(b7), kr); + } + vst1q_u8(out, veorq_u8(veorq_u8(b0, k[0]), prev)); + vst1q_u8(out + 16, veorq_u8(veorq_u8(b1, k[0]), c0)); + vst1q_u8(out + 32, veorq_u8(veorq_u8(b2, k[0]), c1)); + vst1q_u8(out + 48, veorq_u8(veorq_u8(b3, k[0]), c2)); + vst1q_u8(out + 64, veorq_u8(veorq_u8(b4, k[0]), c3)); + vst1q_u8(out + 80, veorq_u8(veorq_u8(b5, k[0]), c4)); + vst1q_u8(out + 96, veorq_u8(veorq_u8(b6, k[0]), c5)); + vst1q_u8(out + 112, veorq_u8(veorq_u8(b7, k[0]), c6)); + prev = c7; + in += 128; + out += 128; + len -= 128; + } + for (; len >= 16; in += 16, out += 16, len -= 16) + { + uint8x16_t c = vld1q_u8(in); + uint8x16_t b = vaesdq_u8(c, k[nr]); + for (r = nr - 1; r > 0; r--) + b = vaesdq_u8(vaesimcq_u8(b), k[r]); + vst1q_u8(out, veorq_u8(veorq_u8(b, k[0]), prev)); + prev = c; + } + vst1q_u8(iv, prev); +} + +/* CTR with eight blocks in flight, the round keys loaded once per + * call. The counter block is kept byte-reversed within each 32-bit + * lane, where its big-endian counter is lane 3 as a native word: one + * add steps it (mod 2^32, as GCM's inc32) and one vrev32 turns it back + * into a block - the other lanes reverse twice to themselves. */ +AES_TARGET_ARM +static void aes_arm_ctr(const struct aes_ctx *ctx, uint8_t *counter, + const uint8_t *in, uint8_t *out, size_t len) +{ + const uint32x4_t z = vdupq_n_u32(0); + const uint32x4_t one = vsetq_lane_u32(1, z, 3); + const uint32x4_t two = vsetq_lane_u32(2, z, 3); + const uint32x4_t four = vsetq_lane_u32(4, z, 3); + const uint32x4_t eight = vsetq_lane_u32(8, z, 3); + uint32x4_t cv = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(counter))); + uint8x16_t rk[15]; + unsigned r, nr = ctx->rounds; + + for (r = 0; r <= nr; r++) + rk[r] = aes_arm_rk(ctx, r); + + while (len >= 128) + { + uint32x4_t c1 = vaddq_u32(cv, one); + uint32x4_t c2 = vaddq_u32(cv, two); + uint32x4_t c3 = vaddq_u32(c1, two); + uint8x16_t b0 = vrev32q_u8(vreinterpretq_u8_u32(cv)); + uint8x16_t b1 = vrev32q_u8(vreinterpretq_u8_u32(c1)); + uint8x16_t b2 = vrev32q_u8(vreinterpretq_u8_u32(c2)); + uint8x16_t b3 = vrev32q_u8(vreinterpretq_u8_u32(c3)); + uint8x16_t b4 = vrev32q_u8(vreinterpretq_u8_u32(vaddq_u32(cv, four))); + uint8x16_t b5 = vrev32q_u8(vreinterpretq_u8_u32(vaddq_u32(c1, four))); + uint8x16_t b6 = vrev32q_u8(vreinterpretq_u8_u32(vaddq_u32(c2, four))); + uint8x16_t b7 = vrev32q_u8(vreinterpretq_u8_u32(vaddq_u32(c3, four))); + for (r = 0; r + 1 < nr; r++) + { + uint8x16_t k = rk[r]; + b0 = vaesmcq_u8(vaeseq_u8(b0, k)); b1 = vaesmcq_u8(vaeseq_u8(b1, k)); + b2 = vaesmcq_u8(vaeseq_u8(b2, k)); b3 = vaesmcq_u8(vaeseq_u8(b3, k)); + b4 = vaesmcq_u8(vaeseq_u8(b4, k)); b5 = vaesmcq_u8(vaeseq_u8(b5, k)); + b6 = vaesmcq_u8(vaeseq_u8(b6, k)); b7 = vaesmcq_u8(vaeseq_u8(b7, k)); + } + vst1q_u8(out, veorq_u8(veorq_u8(vaeseq_u8(b0, rk[nr - 1]), rk[nr]), vld1q_u8(in))); + vst1q_u8(out + 16, veorq_u8(veorq_u8(vaeseq_u8(b1, rk[nr - 1]), rk[nr]), vld1q_u8(in + 16))); + vst1q_u8(out + 32, veorq_u8(veorq_u8(vaeseq_u8(b2, rk[nr - 1]), rk[nr]), vld1q_u8(in + 32))); + vst1q_u8(out + 48, veorq_u8(veorq_u8(vaeseq_u8(b3, rk[nr - 1]), rk[nr]), vld1q_u8(in + 48))); + vst1q_u8(out + 64, veorq_u8(veorq_u8(vaeseq_u8(b4, rk[nr - 1]), rk[nr]), vld1q_u8(in + 64))); + vst1q_u8(out + 80, veorq_u8(veorq_u8(vaeseq_u8(b5, rk[nr - 1]), rk[nr]), vld1q_u8(in + 80))); + vst1q_u8(out + 96, veorq_u8(veorq_u8(vaeseq_u8(b6, rk[nr - 1]), rk[nr]), vld1q_u8(in + 96))); + vst1q_u8(out + 112, veorq_u8(veorq_u8(vaeseq_u8(b7, rk[nr - 1]), rk[nr]), vld1q_u8(in + 112))); + cv = vaddq_u32(cv, eight); + in += 128; + out += 128; + len -= 128; + } + while (len) + { + size_t n = (len < 16) ? len : 16; + size_t i; + uint8_t tmp[16]; + uint8x16_t x = vrev32q_u8(vreinterpretq_u8_u32(cv)); + for (r = 0; r + 1 < nr; r++) + x = vaesmcq_u8(vaeseq_u8(x, rk[r])); + x = veorq_u8(vaeseq_u8(x, rk[nr - 1]), rk[nr]); + if (n == 16) + vst1q_u8(out, veorq_u8(x, vld1q_u8(in))); + else + { + vst1q_u8(tmp, x); + for (i = 0; i < n; i++) + out[i] = in[i] ^ tmp[i]; + } + cv = vaddq_u32(cv, one); + in += n; + out += n; + len -= n; + } + vst1q_u8(counter, vrev32q_u8(vreinterpretq_u8_u32(cv))); +} + +/* GHASH multiply on PMULL: the same byte-reversed-operand algorithm + * as the x86 path, with the SSE lane shifts spelled as vext and the + * word shifts as vshl / vshr. */ +/* The unreduced 256-bit product of a and b, as *lo / *hi. */ +AES_TARGET_ARM +static void aes_arm_clmul(uint8x16_t a, uint8x16_t b, uint8x16_t *lo, uint8x16_t *hi) +{ + const uint8x16_t z = vdupq_n_u8(0); + poly64x2_t pa = vreinterpretq_p64_u8(a); + poly64x2_t pb = vreinterpretq_p64_u8(b); + uint8x16_t t3 = vreinterpretq_u8_p128(vmull_p64((poly64_t)vgetq_lane_p64(pa, 0), (poly64_t)vgetq_lane_p64(pb, 0))); + uint8x16_t t4 = vreinterpretq_u8_p128(vmull_p64((poly64_t)vgetq_lane_p64(pa, 0), (poly64_t)vgetq_lane_p64(pb, 1))); + uint8x16_t t5 = vreinterpretq_u8_p128(vmull_p64((poly64_t)vgetq_lane_p64(pa, 1), (poly64_t)vgetq_lane_p64(pb, 0))); + uint8x16_t t6 = vreinterpretq_u8_p128(vmull_high_p64(pa, pb)); + t4 = veorq_u8(t4, t5); + *lo = veorq_u8(t3, vextq_u8(z, t4, 8)); /* slli_si128 8 */ + *hi = veorq_u8(t6, vextq_u8(t4, z, 8)); /* srli_si128 8 */ +} + +/* The product reduced: one bit left and modulo x^128 + x^7 + x^2 + + * x + 1, both linear, so a sum of products reduces as one. */ +AES_TARGET_ARM +static uint8x16_t aes_arm_reduce(uint8x16_t t3, uint8x16_t t6) +{ + const uint8x16_t z = vdupq_n_u8(0); + uint8x16_t t7, t8, t9, t2, t4, t5; + uint32x4_t w; + + w = vreinterpretq_u32_u8(t3); + t7 = vreinterpretq_u8_u32(vshrq_n_u32(w, 31)); + t3 = vreinterpretq_u8_u32(vshlq_n_u32(w, 1)); + w = vreinterpretq_u32_u8(t6); + t8 = vreinterpretq_u8_u32(vshrq_n_u32(w, 31)); + t6 = vreinterpretq_u8_u32(vshlq_n_u32(w, 1)); + t9 = vextq_u8(t7, z, 12); /* srli_si128 12 */ + t8 = vextq_u8(z, t8, 12); /* slli_si128 4 */ + t7 = vextq_u8(z, t7, 12); /* slli_si128 4 */ + t3 = vorrq_u8(t3, t7); + t6 = vorrq_u8(t6, t8); + t6 = vorrq_u8(t6, t9); + + w = vreinterpretq_u32_u8(t3); + t7 = vreinterpretq_u8_u32(vshlq_n_u32(w, 31)); + t8 = vreinterpretq_u8_u32(vshlq_n_u32(w, 30)); + t9 = vreinterpretq_u8_u32(vshlq_n_u32(w, 25)); + t7 = veorq_u8(t7, t8); + t7 = veorq_u8(t7, t9); + t8 = vextq_u8(t7, z, 4); /* srli_si128 4 */ + t7 = vextq_u8(z, t7, 4); /* slli_si128 12 */ + t3 = veorq_u8(t3, t7); + + w = vreinterpretq_u32_u8(t3); + t2 = vreinterpretq_u8_u32(vshrq_n_u32(w, 1)); + t4 = vreinterpretq_u8_u32(vshrq_n_u32(w, 2)); + t5 = vreinterpretq_u8_u32(vshrq_n_u32(w, 7)); + t2 = veorq_u8(t2, t4); + t2 = veorq_u8(t2, t5); + t2 = veorq_u8(t2, t8); + t3 = veorq_u8(t3, t2); + return veorq_u8(t6, t3); +} + +AES_TARGET_ARM +static uint8x16_t aes_arm_gfmul(uint8x16_t a, uint8x16_t b) +{ + uint8x16_t lo, hi; + aes_arm_clmul(a, b, &lo, &hi); + return aes_arm_reduce(lo, hi); +} + +AES_TARGET_ARM +static void aes_arm_ghash(const struct aes_gcm_ctx *ctx, + uint64_t *y_hi, uint64_t *y_lo, const uint8_t *data, size_t len) +{ + uint8_t o[16]; + uint8x16_t h, y; + + crypto_store64_be(o, ctx->h_hi); + crypto_store64_be(o + 8, ctx->h_lo); + h = vrev64q_u8(vextq_u8(vld1q_u8(o), vld1q_u8(o), 8)); /* full byte reverse */ + crypto_store64_be(o, *y_hi); + crypto_store64_be(o + 8, *y_lo); + y = vrev64q_u8(vextq_u8(vld1q_u8(o), vld1q_u8(o), 8)); + + /* eight blocks per reduction, each product by Karatsuba, the low, + * high and middle terms summed apart and folded once, as on x86 */ + if (len >= 128) + { + const uint8x16_t z = vdupq_n_u8(0); + uint8x16_t hp[8], hk[8]; + unsigned j; + hp[0] = h; + for (j = 1; j < 8; j++) + hp[j] = aes_arm_gfmul(hp[j - 1], h); + for (j = 0; j < 8; j++) + hk[j] = veorq_u8(hp[j], vextq_u8(hp[j], hp[j], 8)); + while (len >= 128) + { + uint8x16_t lo = z, hi = z, mid = z; + for (j = 0; j < 8; j++) + { + uint8x16_t d = vld1q_u8(data + 16 * j); + uint8x16_t dk; + poly64x2_t pd, pp, pdk, ppk; + d = vrev64q_u8(vextq_u8(d, d, 8)); + if (!j) + d = veorq_u8(d, y); + dk = veorq_u8(d, vextq_u8(d, d, 8)); + pd = vreinterpretq_p64_u8(d); + pp = vreinterpretq_p64_u8(hp[7 - j]); + pdk = vreinterpretq_p64_u8(dk); + ppk = vreinterpretq_p64_u8(hk[7 - j]); + lo = veorq_u8(lo, vreinterpretq_u8_p128(vmull_p64((poly64_t)vgetq_lane_p64(pd, 0), (poly64_t)vgetq_lane_p64(pp, 0)))); + hi = veorq_u8(hi, vreinterpretq_u8_p128(vmull_high_p64(pd, pp))); + mid = veorq_u8(mid, vreinterpretq_u8_p128(vmull_p64((poly64_t)vgetq_lane_p64(pdk, 0), (poly64_t)vgetq_lane_p64(ppk, 0)))); + } + mid = veorq_u8(mid, veorq_u8(lo, hi)); + lo = veorq_u8(lo, vextq_u8(z, mid, 8)); /* slli_si128 8 */ + hi = veorq_u8(hi, vextq_u8(mid, z, 8)); /* srli_si128 8 */ + y = aes_arm_reduce(lo, hi); + data += 128; + len -= 128; + } + } + + while (len >= 16) + { + uint8x16_t d = vld1q_u8(data); + d = vrev64q_u8(vextq_u8(d, d, 8)); + y = aes_arm_gfmul(veorq_u8(y, d), h); + data += 16; + len -= 16; + } + if (len) + { + uint8x16_t d; + memset(o, 0, 16); + memcpy(o, data, len); + d = vld1q_u8(o); + d = vrev64q_u8(vextq_u8(d, d, 8)); + y = aes_arm_gfmul(veorq_u8(y, d), h); + } + + vst1q_u8(o, vrev64q_u8(vextq_u8(y, y, 8))); + *y_hi = crypto_load64_be(o); + *y_lo = crypto_load64_be(o + 8); +} +#endif + +int crypto_aes_hw(void) +{ +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + return 1; +#endif +#if defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + return 1; +#endif + return 0; +} + +int aes_init(struct aes_ctx *ctx, const uint8_t *key, size_t key_len) +{ + unsigned i; + unsigned nk; + unsigned total; + + switch (key_len) + { + case 16: nk = 4; ctx->rounds = 10; break; + case 24: nk = 6; ctx->rounds = 12; break; + case 32: nk = 8; ctx->rounds = 14; break; + default: + return -1; + } + + total = 4 * (ctx->rounds + 1); + + for (i = 0; i < nk; i++) + ctx->rk[i] = crypto_load32_be(key + 4 * i); + + for (i = nk; i < total; i++) + { + uint32_t t = ctx->rk[i - 1]; + if ((i % nk) == 0) + t = aes_sub_word(crypto_rotl32(t, 8)) ^ ((uint32_t)aes_rcon[i / nk - 1] << 24); + else if (nk > 6 && (i % nk) == 4) + t = aes_sub_word(t); + ctx->rk[i] = ctx->rk[i - nk] ^ t; + } + + return 0; +} + +static void aes_encrypt_block_c(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ + const uint32_t *rk = ctx->rk; + uint32_t s0, s1, s2, s3, t0, t1, t2, t3; + unsigned r; + + s0 = crypto_load32_be(in) ^ rk[0]; + s1 = crypto_load32_be(in + 4) ^ rk[1]; + s2 = crypto_load32_be(in + 8) ^ rk[2]; + s3 = crypto_load32_be(in + 12) ^ rk[3]; + rk += 4; + +#define AES_ROUND(o0, o1, o2, o3, i0, i1, i2, i3) \ + o0 = aes_ft[0][i0 >> 24] ^ aes_ft[1][(i1 >> 16) & 0xff] \ + ^ aes_ft[2][(i2 >> 8) & 0xff] ^ aes_ft[3][i3 & 0xff] ^ rk[0]; \ + o1 = aes_ft[0][i1 >> 24] ^ aes_ft[1][(i2 >> 16) & 0xff] \ + ^ aes_ft[2][(i3 >> 8) & 0xff] ^ aes_ft[3][i0 & 0xff] ^ rk[1]; \ + o2 = aes_ft[0][i2 >> 24] ^ aes_ft[1][(i3 >> 16) & 0xff] \ + ^ aes_ft[2][(i0 >> 8) & 0xff] ^ aes_ft[3][i1 & 0xff] ^ rk[2]; \ + o3 = aes_ft[0][i3 >> 24] ^ aes_ft[1][(i0 >> 16) & 0xff] \ + ^ aes_ft[2][(i1 >> 8) & 0xff] ^ aes_ft[3][i2 & 0xff] ^ rk[3]; \ + rk += 4 + + for (r = 1; r < ctx->rounds; r += 2) + { + AES_ROUND(t0, t1, t2, t3, s0, s1, s2, s3); + if (r + 1 == ctx->rounds) + { + s0 = t0; s1 = t1; s2 = t2; s3 = t3; + break; + } + AES_ROUND(s0, s1, s2, s3, t0, t1, t2, t3); + } +#undef AES_ROUND + + /* Last round: SubBytes + ShiftRows only. */ + t0 = ((uint32_t)aes_sbox[s0 >> 24] << 24) ^ ((uint32_t)aes_sbox[(s1 >> 16) & 0xff] << 16) + ^ ((uint32_t)aes_sbox[(s2 >> 8) & 0xff] << 8) ^ aes_sbox[s3 & 0xff] ^ rk[0]; + t1 = ((uint32_t)aes_sbox[s1 >> 24] << 24) ^ ((uint32_t)aes_sbox[(s2 >> 16) & 0xff] << 16) + ^ ((uint32_t)aes_sbox[(s3 >> 8) & 0xff] << 8) ^ aes_sbox[s0 & 0xff] ^ rk[1]; + t2 = ((uint32_t)aes_sbox[s2 >> 24] << 24) ^ ((uint32_t)aes_sbox[(s3 >> 16) & 0xff] << 16) + ^ ((uint32_t)aes_sbox[(s0 >> 8) & 0xff] << 8) ^ aes_sbox[s1 & 0xff] ^ rk[2]; + t3 = ((uint32_t)aes_sbox[s3 >> 24] << 24) ^ ((uint32_t)aes_sbox[(s0 >> 16) & 0xff] << 16) + ^ ((uint32_t)aes_sbox[(s1 >> 8) & 0xff] << 8) ^ aes_sbox[s2 & 0xff] ^ rk[3]; + + crypto_store32_be(out, t0); + crypto_store32_be(out + 4, t1); + crypto_store32_be(out + 8, t2); + crypto_store32_be(out + 12, t3); +} + +/* InvMixColumns of one column: each output byte is the matching + * byte of imc[b] rotated into place. */ +static uint32_t aes_imc_col(uint32_t c) +{ + uint32_t t; + t = aes_imc[(c >> 24) & 0xff]; + t ^= crypto_rotl32(aes_imc[(c >> 16) & 0xff], 24); + t ^= crypto_rotl32(aes_imc[(c >> 8) & 0xff], 16); + t ^= crypto_rotl32(aes_imc[ c & 0xff], 8); + return t; +} + +/* Inverse cipher, standard order: AddRoundKey with the last round key, + * then per round InvShiftRows + InvSubBytes, AddRoundKey, and + * InvMixColumns on the state (skipped for the final round). */ +static void aes_decrypt_block_c(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ + const uint32_t *rk = ctx->rk + 4 * ctx->rounds; + uint32_t s0, s1, s2, s3, t0, t1, t2, t3; + unsigned r; + + s0 = crypto_load32_be(in) ^ rk[0]; + s1 = crypto_load32_be(in + 4) ^ rk[1]; + s2 = crypto_load32_be(in + 8) ^ rk[2]; + s3 = crypto_load32_be(in + 12) ^ rk[3]; + + for (r = ctx->rounds; r > 0; r--) + { + rk -= 4; + /* InvShiftRows moves row i right by i; column j gathers byte + * row i from column (j - i) mod 4 */ + t0 = ((uint32_t)aes_isbox[(s0 >> 24) & 0xff] << 24) + | ((uint32_t)aes_isbox[(s3 >> 16) & 0xff] << 16) + | ((uint32_t)aes_isbox[(s2 >> 8) & 0xff] << 8) + | (uint32_t)aes_isbox[ s1 & 0xff]; + t1 = ((uint32_t)aes_isbox[(s1 >> 24) & 0xff] << 24) + | ((uint32_t)aes_isbox[(s0 >> 16) & 0xff] << 16) + | ((uint32_t)aes_isbox[(s3 >> 8) & 0xff] << 8) + | (uint32_t)aes_isbox[ s2 & 0xff]; + t2 = ((uint32_t)aes_isbox[(s2 >> 24) & 0xff] << 24) + | ((uint32_t)aes_isbox[(s1 >> 16) & 0xff] << 16) + | ((uint32_t)aes_isbox[(s0 >> 8) & 0xff] << 8) + | (uint32_t)aes_isbox[ s3 & 0xff]; + t3 = ((uint32_t)aes_isbox[(s3 >> 24) & 0xff] << 24) + | ((uint32_t)aes_isbox[(s2 >> 16) & 0xff] << 16) + | ((uint32_t)aes_isbox[(s1 >> 8) & 0xff] << 8) + | (uint32_t)aes_isbox[ s0 & 0xff]; + t0 ^= rk[0]; t1 ^= rk[1]; t2 ^= rk[2]; t3 ^= rk[3]; + if (r > 1) + { + s0 = aes_imc_col(t0); s1 = aes_imc_col(t1); + s2 = aes_imc_col(t2); s3 = aes_imc_col(t3); + } + else + { + s0 = t0; s1 = t1; s2 = t2; s3 = t3; + } + } + + crypto_store32_be(out, s0); + crypto_store32_be(out + 4, s1); + crypto_store32_be(out + 8, s2); + crypto_store32_be(out + 12, s3); +} + +void aes_decrypt_block(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + { + aes_x86_decrypt_block(ctx, in, out); + return; + } +#elif defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + { + aes_arm_decrypt_block(ctx, in, out); + return; + } +#endif + aes_decrypt_block_c(ctx, in, out); +} + +void aes_encrypt_block(const struct aes_ctx *ctx, + const uint8_t *in, uint8_t *out) +{ +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + { + aes_x86_encrypt_block(ctx, in, out); + return; + } +#elif defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + { + aes_arm_encrypt_block(ctx, in, out); + return; + } +#endif + aes_encrypt_block_c(ctx, in, out); +} + +static void aes_ctr_inc32(uint8_t *counter) +{ + uint32_t v = crypto_load32_be(counter + 12) + 1; + crypto_store32_be(counter + 12, v); +} + +void aes_ctr_crypt(const struct aes_ctx *ctx, uint8_t *counter, + const uint8_t *in, uint8_t *out, size_t len) +{ + uint8_t ks[16]; + +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + { + aes_x86_ctr(ctx, counter, in, out, len); + return; + } +#elif defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + { + aes_arm_ctr(ctx, counter, in, out, len); + return; + } +#endif + while (len) + { + size_t n = (len < 16) ? len : 16; + size_t i; + + aes_encrypt_block(ctx, counter, ks); + aes_ctr_inc32(counter); + + for (i = 0; i < n; i++) + out[i] = in[i] ^ ks[i]; + + in += n; + out += n; + len -= n; + } + + crypto_memzero(ks, sizeof(ks)); +} + +void aes_cbc_encrypt(const struct aes_ctx *ctx, uint8_t *iv, + const uint8_t *in, uint8_t *out, size_t len) +{ + aes_cbc_encrypt_lanes(ctx, 1, &iv, &in, &out, len); +} + +void aes_cbc_encrypt_lanes(const struct aes_ctx *ctx, unsigned n, + uint8_t *const *iv, const uint8_t *const *in, uint8_t *const *out, + size_t len) +{ + unsigned l; + +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + { + aes_x86_cbc_lanes(ctx, n, iv, in, out, len); + return; + } +#elif defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + { + aes_arm_cbc_lanes(ctx, n, iv, in, out, len); + return; + } +#endif + for (l = 0; l < n; l++) + { + size_t off; + unsigned i; + uint8_t x[16]; + memcpy(x, iv[l], 16); + for (off = 0; off < len; off += 16) + { + for (i = 0; i < 16; i++) + x[i] ^= in[l][off + i]; + aes_encrypt_block(ctx, x, x); + memcpy(out[l] + off, x, 16); + } + memcpy(iv[l], x, 16); + } +} + +void aes_cbc_decrypt(const struct aes_ctx *ctx, uint8_t *iv, + const uint8_t *in, uint8_t *out, size_t len) +{ + uint8_t prev[16], c[16]; + size_t off; + unsigned i; + +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + { + aes_x86_cbc_decrypt(ctx, iv, in, out, len); + return; + } +#elif defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + { + aes_arm_cbc_decrypt(ctx, iv, in, out, len); + return; + } +#endif + memcpy(prev, iv, 16); + for (off = 0; off + 16 <= len; off += 16) + { + memcpy(c, in + off, 16); + aes_decrypt_block_c(ctx, c, out + off); + for (i = 0; i < 16; i++) + out[off + i] ^= prev[i]; + memcpy(prev, c, 16); + } + memcpy(iv, prev, 16); +} + +/* Double in GF(2^128) with the CMAC polynomial. */ +static void aes_cmac_dbl(uint8_t *b) +{ + unsigned i; + uint8_t carry = 0; + uint8_t msb = b[0] >> 7; + + for (i = 16; i-- > 0; ) + { + uint8_t next = b[i] >> 7; + b[i] = (uint8_t)((b[i] << 1) | carry); + carry = next; + } + b[15] ^= (uint8_t)(0x87 & (0 - msb)); +} + +void aes_cmac_init(struct aes_cmac_ctx *s, const struct aes_ctx *aes) +{ + s->aes = aes; + s->n = 0; + memset(s->x, 0, 16); +} + +/* The last block takes a subkey, so a full block is held back until + * more of the message shows it was not the last. */ +void aes_cmac_update(struct aes_cmac_ctx *s, const uint8_t *msg, size_t len) +{ + unsigned i; + /* the chain in a local for the call: through @s, every octet of it + * may alias @msg and the block XOR is not vectorised */ + uint8_t x[16]; + + if (!len) + return; + memcpy(x, s->x, 16); + if (s->n) + { + size_t take = 16 - s->n; + if (take > len) + take = len; + memcpy(s->buf + s->n, msg, take); + s->n += take; + msg += take; + len -= take; + if (!len) + return; + for (i = 0; i < 16; i++) + x[i] ^= s->buf[i]; + aes_encrypt_block(s->aes, x, x); + s->n = 0; + } + while (len > 16) + { + for (i = 0; i < 16; i++) + x[i] ^= msg[i]; + aes_encrypt_block(s->aes, x, x); + msg += 16; + len -= 16; + } + memcpy(s->buf, msg, len); + s->n = len; + memcpy(s->x, x, 16); + crypto_memzero(x, sizeof(x)); +} + +void aes_cmac_final(struct aes_cmac_ctx *s, uint8_t *mac) +{ + unsigned i; + uint8_t k[16]; + + memset(k, 0, 16); + aes_encrypt_block(s->aes, k, k); + aes_cmac_dbl(k); + if (s->n != 16) + { + /* Partial or empty last block: pad 10* and use K2 = dbl(K1). */ + aes_cmac_dbl(k); + memset(s->buf + s->n, 0, 16 - s->n); + s->buf[s->n] = 0x80; + } + for (i = 0; i < 16; i++) + s->x[i] ^= s->buf[i] ^ k[i]; + aes_encrypt_block(s->aes, s->x, mac); + + crypto_memzero(k, sizeof(k)); + crypto_memzero(s, sizeof(*s)); +} + +void aes_cmac(const struct aes_ctx *ctx, + const uint8_t *msg, size_t len, uint8_t *mac) +{ + unsigned i; + size_t nblocks; + uint8_t k1[16]; + uint8_t x[16]; + uint8_t last[16]; + + memset(x, 0, 16); + aes_encrypt_block(ctx, x, k1); + aes_cmac_dbl(k1); + + nblocks = (len + 15) / 16; + if (nblocks == 0) + nblocks = 1; + + /* Every block but the last, straight through the chain. */ + while (nblocks > 1) + { + for (i = 0; i < 16; i++) + x[i] ^= msg[i]; + aes_encrypt_block(ctx, x, x); + msg += 16; + len -= 16; + nblocks--; + } + + if (len == 16) + memcpy(last, msg, 16); + else + { + /* Partial or empty last block: pad 10* and use K2 = dbl(K1). */ + aes_cmac_dbl(k1); + memset(last, 0, 16); + memcpy(last, msg, len); + last[len] = 0x80; + } + + for (i = 0; i < 16; i++) + x[i] ^= last[i] ^ k1[i]; + aes_encrypt_block(ctx, x, mac); + + crypto_memzero(k1, sizeof(k1)); + crypto_memzero(x, sizeof(x)); + crypto_memzero(last, sizeof(last)); +} + +/* ---- CCM (RFC 3610) ---------------------------------------------- */ + +/* CBC-MAC over B_0 || AAD (length-prefixed, padded) || payload + * (padded), then CTR with A_i counters; the tag is the MAC under + * counter zero. */ +int aes_ccm_stream_init(struct aes_ccm_stream *s, const struct aes_ctx *ctx, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + size_t msg_len, size_t tag_len) +{ + size_t L, i, off; + size_t remaining; + uint8_t *x = s->x; + + if (nonce_len < 7 || nonce_len > 13 || tag_len < 4 || tag_len > 16 || (tag_len & 1)) + return -1; + L = 15 - nonce_len; + /* the length must fit L octets; shifted as 64 bits, since a 32-bit + * size_t shifted by 32 (L = 4, the 11-octet nonce SMB uses) is + * undefined and comes back unshifted on MIPS */ + if (L < 8 && ((uint64_t)msg_len >> (8 * L)) != 0) + return -1; + s->aes = ctx; + s->len = msg_len; + s->off = 0; + s->L = L; + s->tag_len = tag_len; + + /* B_0: flags | nonce | length of the message */ + x[0] = (uint8_t)((aad_len ? 0x40 : 0) | (((tag_len - 2) / 2) << 3) | (L - 1)); + memcpy(x + 1, nonce, nonce_len); + remaining = msg_len; + for (i = 0; i < L; i++) + { + x[15 - i] = (uint8_t)remaining; + remaining >>= 8; + } + aes_encrypt_block(ctx, x, x); + + /* AAD, length-prefixed (2 or 6 octets here; > 2^32 is not needed) */ + if (aad_len) + { + uint8_t blk[16]; + size_t n; + memset(blk, 0, 16); + if (aad_len < 0xff00) + { + blk[0] = (uint8_t)(aad_len >> 8); + blk[1] = (uint8_t)aad_len; + off = 2; + } + else + { + blk[0] = 0xff; blk[1] = 0xfe; + crypto_store32_be(blk + 2, (uint32_t)aad_len); + off = 6; + } + n = 16 - off; + if (n > aad_len) + n = aad_len; + memcpy(blk + off, aad, n); + for (i = 0; i < 16; i++) + x[i] ^= blk[i]; + aes_encrypt_block(ctx, x, x); + aad += n; + aad_len -= n; + while (aad_len) + { + n = aad_len < 16 ? aad_len : 16; + for (i = 0; i < n; i++) + x[i] ^= aad[i]; + aes_encrypt_block(ctx, x, x); + aad += n; + aad_len -= n; + } + } + + /* Counter blocks: flags = L - 1, nonce, counter */ + memset(s->ctr, 0, 16); + s->ctr[0] = (uint8_t)(L - 1); + memcpy(s->ctr + 1, nonce, nonce_len); + aes_encrypt_block(ctx, s->ctr, s->s0); /* S_0, for the tag */ + return 0; +} + +/* A piece that starts or ends inside a block carries its keystream + * and the MAC chain across in @s; the chain is run when the block + * fills or the message ends. The chain, counter and keystream are + * locals for the call - through @s every octet may alias the data and + * the block loops are not vectorised. */ +static void aes_ccm_stream_run(struct aes_ccm_stream *s, int encrypt, + const uint8_t *in, uint8_t *out, size_t len) +{ + uint8_t x[16], ctr[16], ks[16]; + size_t i, L = s->L, off = s->off; + + if (len > s->len - off) + len = s->len - off; + if (!len) + return; + memcpy(x, s->x, 16); + memcpy(ctr, s->ctr, 16); + memcpy(ks, s->ks, 16); + while (len) + { + size_t pos = off & 15; + size_t n = 16 - pos; + if (n > len) + n = len; + if (!pos) + { + size_t c = off / 16 + 1; + for (i = 0; i < L; i++) + { + ctr[15 - i] = (uint8_t)c; + c >>= 8; + } + aes_encrypt_block(s->aes, ctr, ks); + } + if (n == 16) + { + if (encrypt) + { + for (i = 0; i < 16; i++) + x[i] ^= in[i]; + for (i = 0; i < 16; i++) + out[i] = in[i] ^ ks[i]; + } + else + for (i = 0; i < 16; i++) + { + uint8_t p = (uint8_t)(in[i] ^ ks[i]); + out[i] = p; + x[i] ^= p; + } + } + else if (encrypt) + for (i = 0; i < n; i++) + { + x[pos + i] ^= in[i]; + out[i] = in[i] ^ ks[pos + i]; + } + else + for (i = 0; i < n; i++) + { + out[i] = in[i] ^ ks[pos + i]; + x[pos + i] ^= out[i]; + } + off += n; + if (!(off & 15) || off == s->len) + aes_encrypt_block(s->aes, x, x); + in += n; + out += n; + len -= n; + } + s->off = off; + memcpy(s->x, x, 16); + memcpy(s->ctr, ctr, 16); + memcpy(s->ks, ks, 16); + crypto_memzero(x, sizeof(x)); + crypto_memzero(ks, sizeof(ks)); +} + +void aes_ccm_stream_encrypt(struct aes_ccm_stream *s, + const uint8_t *pt, uint8_t *ct, size_t len) +{ + aes_ccm_stream_run(s, 1, pt, ct, len); +} + +void aes_ccm_stream_decrypt(struct aes_ccm_stream *s, + const uint8_t *ct, uint8_t *pt, size_t len) +{ + aes_ccm_stream_run(s, 0, ct, pt, len); +} + +void aes_ccm_stream_tag(struct aes_ccm_stream *s, uint8_t *tag) +{ + size_t i; + for (i = 0; i < s->tag_len; i++) + tag[i] = s->x[i] ^ s->s0[i]; + crypto_memzero(s, sizeof(*s)); +} + +/* CBC-MAC over B_0 || AAD (length-prefixed, padded) || payload + * (padded), then CTR with A_i counters; the tag is the MAC under + * counter zero. */ +static int aes_ccm_core(const struct aes_ctx *ctx, int encrypt, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *in, size_t len, uint8_t *out, + uint8_t *tag, size_t tag_len) +{ + uint8_t x[16]; + uint8_t ctr[16]; + uint8_t s0[16]; + size_t L, i, off; + size_t remaining; + + if (nonce_len < 7 || nonce_len > 13 || tag_len < 4 || tag_len > 16 || (tag_len & 1)) + return -1; + L = 15 - nonce_len; + /* the length must fit L octets; shifted as 64 bits, since a 32-bit + * size_t shifted by 32 (L = 4, the 11-octet nonce SMB uses) is + * undefined and comes back unshifted on MIPS */ + if (L < 8 && ((uint64_t)len >> (8 * L)) != 0) + return -1; + + /* B_0: flags | nonce | length of the message */ + x[0] = (uint8_t)((aad_len ? 0x40 : 0) | (((tag_len - 2) / 2) << 3) | (L - 1)); + memcpy(x + 1, nonce, nonce_len); + remaining = len; + for (i = 0; i < L; i++) + { + x[15 - i] = (uint8_t)remaining; + remaining >>= 8; + } + aes_encrypt_block(ctx, x, x); + + /* AAD, length-prefixed (2 or 6 octets here; > 2^32 is not needed) */ + if (aad_len) + { + uint8_t blk[16]; + size_t n; + memset(blk, 0, 16); + if (aad_len < 0xff00) + { + blk[0] = (uint8_t)(aad_len >> 8); + blk[1] = (uint8_t)aad_len; + off = 2; + } + else + { + blk[0] = 0xff; blk[1] = 0xfe; + crypto_store32_be(blk + 2, (uint32_t)aad_len); + off = 6; + } + n = 16 - off; + if (n > aad_len) + n = aad_len; + memcpy(blk + off, aad, n); + for (i = 0; i < 16; i++) + x[i] ^= blk[i]; + aes_encrypt_block(ctx, x, x); + aad += n; + aad_len -= n; + while (aad_len) + { + n = aad_len < 16 ? aad_len : 16; + for (i = 0; i < n; i++) + x[i] ^= aad[i]; + aes_encrypt_block(ctx, x, x); + aad += n; + aad_len -= n; + } + } + + /* Counter blocks: flags = L - 1, nonce, counter */ + memset(ctr, 0, 16); + ctr[0] = (uint8_t)(L - 1); + memcpy(ctr + 1, nonce, nonce_len); + aes_encrypt_block(ctx, ctr, s0); /* S_0, for the tag */ + + off = 0; + while (off < len) + { + uint8_t ks[16]; + size_t n = (len - off < 16) ? len - off : 16; + size_t c = off / 16 + 1; + for (i = 0; i < L; i++) + { + ctr[15 - i] = (uint8_t)c; + c >>= 8; + } + aes_encrypt_block(ctx, ctr, ks); + if (encrypt) + { + for (i = 0; i < n; i++) + x[i] ^= in[off + i]; + aes_encrypt_block(ctx, x, x); + for (i = 0; i < n; i++) + out[off + i] = in[off + i] ^ ks[i]; + } + else + { + for (i = 0; i < n; i++) + { + out[off + i] = in[off + i] ^ ks[i]; + x[i] ^= out[off + i]; + } + aes_encrypt_block(ctx, x, x); + } + off += n; + } + + for (i = 0; i < tag_len; i++) + tag[i] = x[i] ^ s0[i]; + crypto_memzero(x, sizeof(x)); + crypto_memzero(s0, sizeof(s0)); + return 0; +} + +int aes_ccm_encrypt(const struct aes_ctx *ctx, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *pt, size_t pt_len, + uint8_t *ct, uint8_t *tag, size_t tag_len) +{ + return aes_ccm_core(ctx, 1, nonce, nonce_len, aad, aad_len, + pt, pt_len, ct, tag, tag_len); +} + +int aes_ccm_decrypt(const struct aes_ctx *ctx, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *ct, size_t ct_len, + const uint8_t *tag, size_t tag_len, uint8_t *pt) +{ + uint8_t calc[16]; + if (aes_ccm_core(ctx, 0, nonce, nonce_len, aad, aad_len, + ct, ct_len, pt, calc, tag_len) != 0) + return -1; + if (!crypto_memeq_ct(calc, tag, tag_len)) + { + crypto_memzero(pt, ct_len); + return -1; + } + return 0; +} + +/* GHASH, scalar: 4-bit tables of H (16 entries, built at init) and a + * 16-entry reduction table, two lookups per octet. */ +static const uint64_t aes_gcm_last4[16] = { + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0 +}; + +static void aes_gcm_mul(const struct aes_gcm_ctx *ctx, + uint64_t *y_hi, uint64_t *y_lo) +{ + uint8_t x[16]; + uint64_t zh, zl, rem; + int i; + unsigned lo, hi; + + crypto_store64_be(x, *y_hi); + crypto_store64_be(x + 8, *y_lo); + + lo = x[15] & 0x0f; + zh = ctx->hh[lo]; + zl = ctx->hl[lo]; + + for (i = 15; i >= 0; i--) + { + lo = x[i] & 0x0f; + hi = (x[i] >> 4) & 0x0f; + + if (i != 15) + { + rem = zl & 0x0f; + zl = (zh << 60) | (zl >> 4); + zh = (zh >> 4); + zh ^= aes_gcm_last4[rem] << 48; + zh ^= ctx->hh[lo]; + zl ^= ctx->hl[lo]; + } + rem = zl & 0x0f; + zl = (zh << 60) | (zl >> 4); + zh = (zh >> 4); + zh ^= aes_gcm_last4[rem] << 48; + zh ^= ctx->hh[hi]; + zl ^= ctx->hl[hi]; + } + + *y_hi = zh; + *y_lo = zl; +} + +static void aes_gcm_ghash_update(const struct aes_gcm_ctx *ctx, + uint64_t *y_hi, uint64_t *y_lo, const uint8_t *data, size_t len) +{ +#if defined(AES_HAVE_X86_PATH) + if (aes_x86_available()) + { + aes_x86_ghash(ctx, y_hi, y_lo, data, len); + return; + } +#elif defined(AES_HAVE_ARM_PATH) + if (aes_arm_available()) + { + aes_arm_ghash(ctx, y_hi, y_lo, data, len); + return; + } +#endif + while (len) + { + uint8_t blk[16]; + size_t n = (len < 16) ? len : 16; + + if (n < 16) + { + memset(blk, 0, 16); + memcpy(blk, data, n); + data = blk; + } + + *y_hi ^= crypto_load64_be(data); + *y_lo ^= crypto_load64_be(data + 8); + aes_gcm_mul(ctx, y_hi, y_lo); + + data += n; + len -= n; + } +} + +static void aes_gcm_tables(struct aes_gcm_ctx *ctx) +{ + uint64_t vh = ctx->h_hi, vl = ctx->h_lo; + int i, j; + + ctx->hh[0] = 0; ctx->hl[0] = 0; + ctx->hh[8] = vh; ctx->hl[8] = vl; + + for (i = 4; i > 0; i >>= 1) + { + uint32_t t = (uint32_t)(vl & 1) * 0xe1000000u; + vl = (vh << 63) | (vl >> 1); + vh = (vh >> 1) ^ ((uint64_t)t << 32); + ctx->hl[i] = vl; + ctx->hh[i] = vh; + } + for (i = 2; i <= 8; i *= 2) + { + vh = ctx->hh[i]; + vl = ctx->hl[i]; + for (j = 1; j < i; j++) + { + ctx->hh[i + j] = vh ^ ctx->hh[j]; + ctx->hl[i + j] = vl ^ ctx->hl[j]; + } + } +} + +int aes_gcm_init(struct aes_gcm_ctx *ctx, + const uint8_t *key, size_t key_len) +{ + uint8_t h[16]; + + if (aes_init(&ctx->aes, key, key_len) != 0) + return -1; + + memset(h, 0, 16); + aes_encrypt_block(&ctx->aes, h, h); + ctx->h_hi = crypto_load64_be(h); + ctx->h_lo = crypto_load64_be(h + 8); + aes_gcm_tables(ctx); + crypto_memzero(h, sizeof(h)); + return 0; +} + +static void aes_gcm_j0(const struct aes_gcm_ctx *ctx, + const uint8_t *nonce, size_t nonce_len, uint8_t *j0) +{ + if (nonce_len == 12) + { + memcpy(j0, nonce, 12); + j0[12] = 0; j0[13] = 0; j0[14] = 0; j0[15] = 1; + } + else + { + uint64_t y_hi = 0, y_lo = 0; + aes_gcm_ghash_update(ctx, &y_hi, &y_lo, nonce, nonce_len); + y_lo ^= (uint64_t)nonce_len * 8; + aes_gcm_mul(ctx, &y_hi, &y_lo); + crypto_store64_be(j0, y_hi); + crypto_store64_be(j0 + 8, y_lo); + } +} + +static void aes_gcm_tag(const struct aes_gcm_ctx *ctx, const uint8_t *j0, + const uint8_t *aad, size_t aad_len, + const uint8_t *ct, size_t ct_len, uint8_t *tag) +{ + unsigned i; + uint64_t y_hi = 0, y_lo = 0; + uint8_t ek[16]; + + aes_gcm_ghash_update(ctx, &y_hi, &y_lo, aad, aad_len); + aes_gcm_ghash_update(ctx, &y_hi, &y_lo, ct, ct_len); + y_hi ^= (uint64_t)aad_len * 8; + y_lo ^= (uint64_t)ct_len * 8; + aes_gcm_mul(ctx, &y_hi, &y_lo); + + aes_encrypt_block(&ctx->aes, j0, ek); + crypto_store64_be(tag, y_hi); + crypto_store64_be(tag + 8, y_lo); + for (i = 0; i < 16; i++) + tag[i] ^= ek[i]; + crypto_memzero(ek, sizeof(ek)); +} + +int aes_gcm_encrypt(const struct aes_gcm_ctx *ctx, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *pt, size_t pt_len, + uint8_t *ct, uint8_t *tag) +{ + uint8_t j0[16]; + uint8_t ctr[16]; + + aes_gcm_j0(ctx, nonce, nonce_len, j0); + memcpy(ctr, j0, 16); + aes_ctr_inc32(ctr); + aes_ctr_crypt(&ctx->aes, ctr, pt, ct, pt_len); + aes_gcm_tag(ctx, j0, aad, aad_len, ct, pt_len, tag); + return 0; +} + +int aes_gcm_decrypt(const struct aes_gcm_ctx *ctx, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *ct, size_t ct_len, + const uint8_t *tag, uint8_t *pt) +{ + uint8_t j0[16]; + uint8_t ctr[16]; + uint8_t calc[16]; + int ok; + + aes_gcm_j0(ctx, nonce, nonce_len, j0); + aes_gcm_tag(ctx, j0, aad, aad_len, ct, ct_len, calc); + ok = crypto_memeq_ct(calc, tag, 16); + crypto_memzero(calc, sizeof(calc)); + if (!ok) + return -1; + + memcpy(ctr, j0, 16); + aes_ctr_inc32(ctr); + aes_ctr_crypt(&ctx->aes, ctr, ct, pt, ct_len); + return 0; +} + +void aes_gcm_stream_init(struct aes_gcm_stream *s, + const struct aes_gcm_ctx *ctx, const uint8_t *nonce, size_t nonce_len) +{ + s->g = ctx; + s->y_hi = 0; + s->y_lo = 0; + s->aad_len = 0; + s->ct_len = 0; + s->part_len = 0; + aes_gcm_j0(ctx, nonce, nonce_len, s->j0); +} + +/* GHASH takes whole blocks; a piece's tail waits in part for the + * next piece of the same input, and is padded where that input ends. */ +static void aes_gcm_stream_feed(struct aes_gcm_stream *s, + const uint8_t *data, size_t len) +{ + size_t bulk; + + if (s->part_len) + { + size_t take = 16 - s->part_len; + if (take > len) + take = len; + memcpy(s->part + s->part_len, data, take); + s->part_len += take; + data += take; + len -= take; + if (s->part_len < 16) + return; + aes_gcm_ghash_update(s->g, &s->y_hi, &s->y_lo, s->part, 16); + s->part_len = 0; + } + bulk = len & ~(size_t)15; + if (bulk) + aes_gcm_ghash_update(s->g, &s->y_hi, &s->y_lo, data, bulk); + memcpy(s->part, data + bulk, len - bulk); + s->part_len = len - bulk; +} + +static void aes_gcm_stream_pad(struct aes_gcm_stream *s) +{ + if (s->part_len) + { + aes_gcm_ghash_update(s->g, &s->y_hi, &s->y_lo, s->part, s->part_len); + s->part_len = 0; + } +} + +void aes_gcm_stream_aad(struct aes_gcm_stream *s, + const uint8_t *aad, size_t len) +{ + aes_gcm_stream_feed(s, aad, len); + s->aad_len += len; +} + +void aes_gcm_stream_hash_ct(struct aes_gcm_stream *s, + const uint8_t *ct, size_t len) +{ + if (!s->ct_len) + aes_gcm_stream_pad(s); /* the AAD ends here */ + aes_gcm_stream_feed(s, ct, len); + s->ct_len += len; +} + +void aes_gcm_stream_tag(struct aes_gcm_stream *s, uint8_t *tag) +{ + unsigned i; + uint8_t ek[16]; + + aes_gcm_stream_pad(s); + s->y_hi ^= s->aad_len * 8; + s->y_lo ^= s->ct_len * 8; + aes_gcm_mul(s->g, &s->y_hi, &s->y_lo); + aes_encrypt_block(&s->g->aes, s->j0, ek); + crypto_store64_be(tag, s->y_hi); + crypto_store64_be(tag + 8, s->y_lo); + for (i = 0; i < 16; i++) + tag[i] ^= ek[i]; + crypto_memzero(ek, sizeof(ek)); +} + +void aes_gcm_stream_crypt(const struct aes_gcm_stream *s, uint64_t off, + const uint8_t *in, uint8_t *out, size_t len) +{ + uint8_t ctr[16]; + size_t pos = (size_t)(off & 15); + + /* the ciphertext's block b runs under counter J0 + 1 + b */ + memcpy(ctr, s->j0, 16); + crypto_store32_be(ctr + 12, crypto_load32_be(s->j0 + 12) + + 1 + (uint32_t)(off >> 4)); + if (pos) + { + uint8_t ks[16]; + size_t i, n = 16 - pos; + if (n > len) + n = len; + aes_encrypt_block(&s->g->aes, ctr, ks); + aes_ctr_inc32(ctr); + for (i = 0; i < n; i++) + out[i] = in[i] ^ ks[pos + i]; + crypto_memzero(ks, sizeof(ks)); + in += n; + out += n; + len -= n; + } + if (len) + aes_ctr_crypt(&s->g->aes, ctr, in, out, len); + crypto_memzero(ctr, sizeof(ctr)); +} + +#define QR(a, b, c, d) \ + a += b; d ^= a; d = crypto_rotl32(d, 16); \ + c += d; b ^= c; b = crypto_rotl32(b, 12); \ + a += b; d ^= a; d = crypto_rotl32(d, 8); \ + c += d; b ^= c; b = crypto_rotl32(b, 7) + +static void chacha20_core(const uint8_t *key, const uint8_t *nonce, + uint32_t counter, uint32_t *out) +{ + unsigned i; + uint32_t s[16]; + uint32_t x[16]; + + s[0] = 0x61707865; s[1] = 0x3320646e; + s[2] = 0x79622d32; s[3] = 0x6b206574; + for (i = 0; i < 8; i++) + s[4 + i] = crypto_load32_le(key + 4 * i); + s[12] = counter; + s[13] = crypto_load32_le(nonce); + s[14] = crypto_load32_le(nonce + 4); + s[15] = crypto_load32_le(nonce + 8); + + memcpy(x, s, sizeof(x)); + + for (i = 0; i < 10; i++) + { + QR(x[0], x[4], x[ 8], x[12]); + QR(x[1], x[5], x[ 9], x[13]); + QR(x[2], x[6], x[10], x[14]); + QR(x[3], x[7], x[11], x[15]); + QR(x[0], x[5], x[10], x[15]); + QR(x[1], x[6], x[11], x[12]); + QR(x[2], x[7], x[ 8], x[13]); + QR(x[3], x[4], x[ 9], x[14]); + } + + for (i = 0; i < 16; i++) + out[i] = x[i] + s[i]; +} + +void chacha20_block(const uint8_t *key, const uint8_t *nonce, + uint32_t counter, uint8_t *out) +{ + unsigned i; + uint32_t w[16]; + + chacha20_core(key, nonce, counter, w); + for (i = 0; i < 16; i++) + crypto_store32_le(out + 4 * i, w[i]); +} + +#if defined(AES_HAVE_X86_PATH) +static retro_atomic_int_t chacha_x86_ok; + +static int chacha_x86_available(void) +{ + int v = retro_atomic_load_acquire_int(&chacha_x86_ok); + if (!v) + { + v = (cpu_features_get() & RETRO_SIMD_SSSE3) ? 2 : 1; + retro_atomic_store_release_int(&chacha_x86_ok, v); + } + return v == 2; +} + +#define CHACHA_ROTL(v, n) _mm_or_si128(_mm_slli_epi32(v, n), _mm_srli_epi32(v, 32 - (n))) +#define CHACHA_QR4(a, b, c, d) \ + a = _mm_add_epi32(a, b); d = _mm_shuffle_epi8(_mm_xor_si128(d, a), r16); \ + c = _mm_add_epi32(c, d); b = CHACHA_ROTL(_mm_xor_si128(b, c), 12); \ + a = _mm_add_epi32(a, b); d = _mm_shuffle_epi8(_mm_xor_si128(d, a), r8); \ + c = _mm_add_epi32(c, d); b = CHACHA_ROTL(_mm_xor_si128(b, c), 7) + +/* Four blocks at once, one state word per vector and one block per + * lane; the 16- and 8-bit rotations are byte shuffles. @in to @out, + * whole groups of 256 octets; returns the octets done. */ +__attribute__((target("ssse3"))) +static size_t chacha20_x86_xor(const uint32_t *s, uint32_t counter, + const uint8_t *in, uint8_t *out, size_t len) +{ + const __m128i r16 = _mm_set_epi8(13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2); + const __m128i r8 = _mm_set_epi8(14,13,12,15, 10,9,8,11, 6,5,4,7, 2,1,0,3); + size_t done = 0; + + while (len - done >= 256) + { + __m128i x[16], o[16]; + unsigned i, g; + for (i = 0; i < 16; i++) + o[i] = _mm_set1_epi32((int)s[i]); + o[12] = _mm_add_epi32(_mm_set1_epi32((int)counter), _mm_set_epi32(3, 2, 1, 0)); + for (i = 0; i < 16; i++) + x[i] = o[i]; + for (i = 0; i < 10; i++) + { + CHACHA_QR4(x[0], x[4], x[ 8], x[12]); + CHACHA_QR4(x[1], x[5], x[ 9], x[13]); + CHACHA_QR4(x[2], x[6], x[10], x[14]); + CHACHA_QR4(x[3], x[7], x[11], x[15]); + CHACHA_QR4(x[0], x[5], x[10], x[15]); + CHACHA_QR4(x[1], x[6], x[11], x[12]); + CHACHA_QR4(x[2], x[7], x[ 8], x[13]); + CHACHA_QR4(x[3], x[4], x[ 9], x[14]); + } + for (i = 0; i < 16; i++) + x[i] = _mm_add_epi32(x[i], o[i]); + /* four words of each of the four blocks per group: transposed + * back into block order and XORed in place */ + for (g = 0; g < 4; g++) + { + __m128i t0 = _mm_unpacklo_epi32(x[4 * g], x[4 * g + 1]); + __m128i t1 = _mm_unpacklo_epi32(x[4 * g + 2], x[4 * g + 3]); + __m128i t2 = _mm_unpackhi_epi32(x[4 * g], x[4 * g + 1]); + __m128i t3 = _mm_unpackhi_epi32(x[4 * g + 2], x[4 * g + 3]); + const uint8_t *ip = in + done + 16 * g; + uint8_t *op = out + done + 16 * g; + _mm_storeu_si128((__m128i*)(op), _mm_xor_si128(_mm_unpacklo_epi64(t0, t1), _mm_loadu_si128((const __m128i*)(ip)))); + _mm_storeu_si128((__m128i*)(op + 64), _mm_xor_si128(_mm_unpackhi_epi64(t0, t1), _mm_loadu_si128((const __m128i*)(ip + 64)))); + _mm_storeu_si128((__m128i*)(op + 128), _mm_xor_si128(_mm_unpacklo_epi64(t2, t3), _mm_loadu_si128((const __m128i*)(ip + 128)))); + _mm_storeu_si128((__m128i*)(op + 192), _mm_xor_si128(_mm_unpackhi_epi64(t2, t3), _mm_loadu_si128((const __m128i*)(ip + 192)))); + } + counter += 4; + done += 256; + } + return done; +} +#undef CHACHA_QR4 +#undef CHACHA_ROTL +#endif + +/* every AArch64 core, and ARMv7 built for NEON (the Vita, 32-bit + * Android); older GCC spells the macro __ARM_NEON__ */ +#if (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(_MSC_VER) && !defined(CHACHA_NO_NEON) +#include +#define CHACHA_HAVE_NEON 1 +/* rotl by 16 is a halfword swap; the others a shift and a shift-insert */ +#define CHACHA_NROT(v, n) vsriq_n_u32(vshlq_n_u32(v, n), v, 32 - (n)) +#define CHACHA_NQR(a, b, c, d) \ + a = vaddq_u32(a, b); d = vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32(veorq_u32(d, a)))); \ + c = vaddq_u32(c, d); b = CHACHA_NROT(veorq_u32(b, c), 12); \ + a = vaddq_u32(a, b); d = CHACHA_NROT(veorq_u32(d, a), 8); \ + c = vaddq_u32(c, d); b = CHACHA_NROT(veorq_u32(b, c), 7) + +/* Four blocks at once on NEON: one state word per vector and one block + * per lane, as on SSSE3. Whole groups of 256 octets; returns the octets + * done. */ +static size_t chacha20_neon_xor(const uint32_t *s, uint32_t counter, + const uint8_t *in, uint8_t *out, size_t len) +{ + static const uint32_t lane[4] = { 0, 1, 2, 3 }; + size_t done = 0; + + while (len - done >= 256) + { + uint32x4_t x[16], o[16]; + unsigned i, g; + for (i = 0; i < 16; i++) + o[i] = vdupq_n_u32(s[i]); + o[12] = vaddq_u32(vdupq_n_u32(counter), vld1q_u32(lane)); + for (i = 0; i < 16; i++) + x[i] = o[i]; + for (i = 0; i < 10; i++) + { + CHACHA_NQR(x[0], x[4], x[ 8], x[12]); + CHACHA_NQR(x[1], x[5], x[ 9], x[13]); + CHACHA_NQR(x[2], x[6], x[10], x[14]); + CHACHA_NQR(x[3], x[7], x[11], x[15]); + CHACHA_NQR(x[0], x[5], x[10], x[15]); + CHACHA_NQR(x[1], x[6], x[11], x[12]); + CHACHA_NQR(x[2], x[7], x[ 8], x[13]); + CHACHA_NQR(x[3], x[4], x[ 9], x[14]); + } + for (i = 0; i < 16; i++) + x[i] = vaddq_u32(x[i], o[i]); + /* each group of four words back into block order: a 4x4 + * transpose, then XORed in */ + for (g = 0; g < 4; g++) + { + uint32x4x2_t p = vtrnq_u32(x[4 * g], x[4 * g + 1]); + uint32x4x2_t q = vtrnq_u32(x[4 * g + 2], x[4 * g + 3]); + uint32x4_t r0 = vcombine_u32(vget_low_u32(p.val[0]), vget_low_u32(q.val[0])); + uint32x4_t r1 = vcombine_u32(vget_low_u32(p.val[1]), vget_low_u32(q.val[1])); + uint32x4_t r2 = vcombine_u32(vget_high_u32(p.val[0]), vget_high_u32(q.val[0])); + uint32x4_t r3 = vcombine_u32(vget_high_u32(p.val[1]), vget_high_u32(q.val[1])); + const uint8_t *ip = in + done + 16 * g; + uint8_t *op = out + done + 16 * g; + vst1q_u8(op, veorq_u8(vreinterpretq_u8_u32(r0), vld1q_u8(ip))); + vst1q_u8(op + 64, veorq_u8(vreinterpretq_u8_u32(r1), vld1q_u8(ip + 64))); + vst1q_u8(op + 128, veorq_u8(vreinterpretq_u8_u32(r2), vld1q_u8(ip + 128))); + vst1q_u8(op + 192, veorq_u8(vreinterpretq_u8_u32(r3), vld1q_u8(ip + 192))); + } + counter += 4; + done += 256; + } + return done; +} +#undef CHACHA_NQR +#undef CHACHA_NROT +#endif + +void chacha20_xor(const uint8_t *key, const uint8_t *nonce, + uint32_t counter, const uint8_t *in, uint8_t *out, size_t len) +{ + uint32_t w[16]; + unsigned i; + +#if defined(AES_HAVE_X86_PATH) || defined(CHACHA_HAVE_NEON) +#if defined(AES_HAVE_X86_PATH) + if (len >= 256 && chacha_x86_available()) +#else + if (len >= 256) +#endif + { + uint32_t s[16]; + size_t done; + s[0] = 0x61707865; s[1] = 0x3320646e; + s[2] = 0x79622d32; s[3] = 0x6b206574; + for (i = 0; i < 8; i++) + s[4 + i] = crypto_load32_le(key + 4 * i); + s[12] = counter; + s[13] = crypto_load32_le(nonce); + s[14] = crypto_load32_le(nonce + 4); + s[15] = crypto_load32_le(nonce + 8); +#if defined(AES_HAVE_X86_PATH) + done = chacha20_x86_xor(s, counter, in, out, len); +#else + done = chacha20_neon_xor(s, counter, in, out, len); +#endif + counter += (uint32_t)(done / 64); + in += done; + out += done; + len -= done; + crypto_memzero(s, sizeof(s)); + } +#endif + + /* Whole blocks a word at a time, no keystream bounce buffer. */ + while (len >= 64) + { + chacha20_core(key, nonce, counter, w); + counter++; + for (i = 0; i < 16; i++) + crypto_store32_le(out + 4 * i, crypto_load32_le(in + 4 * i) ^ w[i]); + in += 64; + out += 64; + len -= 64; + } + if (len) + { + uint8_t ks[64]; + chacha20_block(key, nonce, counter, ks); + for (i = 0; i < len; i++) + out[i] = in[i] ^ ks[i]; + crypto_memzero(ks, sizeof(ks)); + } + crypto_memzero(w, sizeof(w)); +} + +#define P1305_MASK 0x3ffffff + +void poly1305_init(struct poly1305_ctx *ctx, const uint8_t *key) +{ + uint32_t t0 = crypto_load32_le(key); + uint32_t t1 = crypto_load32_le(key + 4); + uint32_t t2 = crypto_load32_le(key + 8); + uint32_t t3 = crypto_load32_le(key + 12); + + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff, split into 26-bit limbs. */ + ctx->r[0] = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; + ctx->r[1] = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; + ctx->r[2] = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; + ctx->r[3] = t2 & 0x3f03fff; t3 >>= 8; + ctx->r[4] = t3 & 0x00fffff; + + ctx->h[0] = ctx->h[1] = ctx->h[2] = ctx->h[3] = ctx->h[4] = 0; + + ctx->pad[0] = crypto_load32_le(key + 16); + ctx->pad[1] = crypto_load32_le(key + 20); + ctx->pad[2] = crypto_load32_le(key + 24); + ctx->pad[3] = crypto_load32_le(key + 28); + + ctx->leftover = 0; + ctx->final = 0; +} + +#if defined(__SIZEOF_INT128__) && !defined(POLY1305_NO_128) +/* 64-bit machines: the same arithmetic in 44-bit limbs, 9 multiplies + * per block instead of 25. The context keeps 26-bit limbs; they are + * turned into 44-bit ones on the way in and back on the way out, which + * costs a few operations a call against every block's savings. */ +__extension__ typedef unsigned __int128 p1305_u128; +#define P1305_M44 ((uint64_t)0xfffffffffffULL) +#define P1305_M42 ((uint64_t)0x3ffffffffffULL) + +static void poly1305_blocks_44(struct poly1305_ctx *ctx, + const uint8_t *m, size_t len) +{ + const uint64_t hibit = ctx->final ? 0 : ((uint64_t)1 << 40); + uint64_t r0, r1, r2, s1, s2, h0, h1, h2, lo; + uint32_t c, g[5]; + p1305_u128 up; + unsigned i; + + /* r: 124 bits, its 26-bit limbs exact */ + lo = (uint64_t)ctx->r[0] | ((uint64_t)ctx->r[1] << 26) | ((uint64_t)ctx->r[2] << 52); + up = (p1305_u128)(ctx->r[2] >> 12) | ((p1305_u128)ctx->r[3] << 14) + | ((p1305_u128)ctx->r[4] << 40); + r0 = lo & P1305_M44; + r1 = ((lo >> 44) | (uint64_t)(up << 20)) & P1305_M44; + r2 = (uint64_t)(up >> 24) & P1305_M42; + s1 = r1 * (5 << 2); + s2 = r2 * (5 << 2); + + /* h: partly reduced 26-bit limbs, carried first so each fits */ + for (i = 0; i < 5; i++) + g[i] = ctx->h[i]; + c = g[0] >> 26; g[0] &= P1305_MASK; g[1] += c; + c = g[1] >> 26; g[1] &= P1305_MASK; g[2] += c; + c = g[2] >> 26; g[2] &= P1305_MASK; g[3] += c; + c = g[3] >> 26; g[3] &= P1305_MASK; g[4] += c; + lo = (uint64_t)g[0] | ((uint64_t)g[1] << 26) | ((uint64_t)(g[2] & 0xfff) << 52); + up = (p1305_u128)(g[2] >> 12) + ((p1305_u128)g[3] << 14) + ((p1305_u128)g[4] << 40); + h0 = lo & P1305_M44; + h1 = ((lo >> 44) | (uint64_t)(up << 20)) & P1305_M44; + h2 = (uint64_t)(up >> 24); + + while (len >= 16) + { + uint64_t t0 = (uint64_t)crypto_load32_le(m) | ((uint64_t)crypto_load32_le(m + 4) << 32); + uint64_t t1 = (uint64_t)crypto_load32_le(m + 8) | ((uint64_t)crypto_load32_le(m + 12) << 32); + p1305_u128 d0, d1, d2; + uint64_t cc; + + h0 += t0 & P1305_M44; + h1 += ((t0 >> 44) | (t1 << 20)) & P1305_M44; + h2 += ((t1 >> 24) & P1305_M42) | hibit; + + d0 = (p1305_u128)h0 * r0 + (p1305_u128)h1 * s2 + (p1305_u128)h2 * s1; + d1 = (p1305_u128)h0 * r1 + (p1305_u128)h1 * r0 + (p1305_u128)h2 * s2; + d2 = (p1305_u128)h0 * r2 + (p1305_u128)h1 * r1 + (p1305_u128)h2 * r0; + + cc = (uint64_t)(d0 >> 44); h0 = (uint64_t)d0 & P1305_M44; + d1 += cc; cc = (uint64_t)(d1 >> 44); h1 = (uint64_t)d1 & P1305_M44; + d2 += cc; cc = (uint64_t)(d2 >> 42); h2 = (uint64_t)d2 & P1305_M42; + h0 += cc * 5; cc = h0 >> 44; h0 &= P1305_M44; + h1 += cc; + + m += 16; + len -= 16; + } + + /* back to 26-bit limbs: h0 | h1 << 44 | h2 << 88 */ + ctx->h[0] = (uint32_t)(h0 & P1305_MASK); + ctx->h[1] = (uint32_t)(((h0 >> 26) | (h1 << 18)) & P1305_MASK); + ctx->h[2] = (uint32_t)((h1 >> 8) & P1305_MASK); + ctx->h[3] = (uint32_t)(((h1 >> 34) | (h2 << 10)) & P1305_MASK); + ctx->h[4] = (uint32_t)(h2 >> 16); +} +#endif + +static void poly1305_blocks(struct poly1305_ctx *ctx, + const uint8_t *m, size_t len) +{ +#if defined(__SIZEOF_INT128__) && !defined(POLY1305_NO_128) + poly1305_blocks_44(ctx, m, len); +} +#else + const uint32_t hibit = ctx->final ? 0 : ((uint32_t)1 << 24); + uint32_t r0 = ctx->r[0], r1 = ctx->r[1], r2 = ctx->r[2]; + uint32_t r3 = ctx->r[3], r4 = ctx->r[4]; + uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5; + uint32_t h0 = ctx->h[0], h1 = ctx->h[1], h2 = ctx->h[2]; + uint32_t h3 = ctx->h[3], h4 = ctx->h[4]; + + while (len >= 16) + { + uint64_t d0, d1, d2, d3, d4; + uint32_t c; + uint32_t t0 = crypto_load32_le(m); + uint32_t t1 = crypto_load32_le(m + 4); + uint32_t t2 = crypto_load32_le(m + 8); + uint32_t t3 = crypto_load32_le(m + 12); + + /* h += m[i] */ + h0 += t0 & P1305_MASK; + h1 += ((t0 >> 26) | (t1 << 6)) & P1305_MASK; + h2 += ((t1 >> 20) | (t2 << 12)) & P1305_MASK; + h3 += ((t2 >> 14) | (t3 << 18)) & P1305_MASK; + h4 += (t3 >> 8) | hibit; + + /* h *= r, mod 2^130 - 5 */ + d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 + + (uint64_t)h3 * s2 + (uint64_t)h4 * s1; + d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 + + (uint64_t)h3 * s3 + (uint64_t)h4 * s2; + d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 + + (uint64_t)h3 * s4 + (uint64_t)h4 * s3; + d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 + + (uint64_t)h3 * r0 + (uint64_t)h4 * s4; + d4 = (uint64_t)h0 * r4 + (uint64_t)h1 * r3 + (uint64_t)h2 * r2 + + (uint64_t)h3 * r1 + (uint64_t)h4 * r0; + + /* partial reduction */ + c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & P1305_MASK; + d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & P1305_MASK; + d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & P1305_MASK; + d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & P1305_MASK; + d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & P1305_MASK; + h0 += c * 5; c = h0 >> 26; h0 &= P1305_MASK; + h1 += c; + + m += 16; + len -= 16; + } + + ctx->h[0] = h0; ctx->h[1] = h1; ctx->h[2] = h2; + ctx->h[3] = h3; ctx->h[4] = h4; +} +#endif + +void poly1305_update(struct poly1305_ctx *ctx, + const uint8_t *m, size_t len) +{ + size_t want; + + if (ctx->leftover) + { + want = 16 - ctx->leftover; + if (want > len) + want = len; + memcpy(ctx->buffer + ctx->leftover, m, want); + len -= want; + m += want; + ctx->leftover += want; + if (ctx->leftover < 16) + return; + poly1305_blocks(ctx, ctx->buffer, 16); + ctx->leftover = 0; + } + + if (len >= 16) + { + want = len & ~(size_t)15; + poly1305_blocks(ctx, m, want); + m += want; + len -= want; + } + + if (len) + { + memcpy(ctx->buffer, m, len); + ctx->leftover = len; + } +} + +void poly1305_final(struct poly1305_ctx *ctx, uint8_t *tag) +{ + uint32_t h0, h1, h2, h3, h4, c; + uint32_t g0, g1, g2, g3, g4; + uint64_t f; + uint32_t mask; + + if (ctx->leftover) + { + size_t i = ctx->leftover; + ctx->buffer[i++] = 1; + for (; i < 16; i++) + ctx->buffer[i] = 0; + ctx->final = 1; + poly1305_blocks(ctx, ctx->buffer, 16); + } + + h0 = ctx->h[0]; h1 = ctx->h[1]; h2 = ctx->h[2]; + h3 = ctx->h[3]; h4 = ctx->h[4]; + + /* full carry */ + c = h1 >> 26; h1 &= P1305_MASK; + h2 += c; c = h2 >> 26; h2 &= P1305_MASK; + h3 += c; c = h3 >> 26; h3 &= P1305_MASK; + h4 += c; c = h4 >> 26; h4 &= P1305_MASK; + h0 += c * 5; c = h0 >> 26; h0 &= P1305_MASK; + h1 += c; + + /* g = h + 5 - 2^130; select g when it did not go negative */ + g0 = h0 + 5; c = g0 >> 26; g0 &= P1305_MASK; + g1 = h1 + c; c = g1 >> 26; g1 &= P1305_MASK; + g2 = h2 + c; c = g2 >> 26; g2 &= P1305_MASK; + g3 = h3 + c; c = g3 >> 26; g3 &= P1305_MASK; + g4 = h4 + c - ((uint32_t)1 << 26); + + mask = (g4 >> 31) - 1; + g0 &= mask; g1 &= mask; g2 &= mask; g3 &= mask; g4 &= mask; + mask = ~mask; + h0 = (h0 & mask) | g0; + h1 = (h1 & mask) | g1; + h2 = (h2 & mask) | g2; + h3 = (h3 & mask) | g3; + h4 = (h4 & mask) | g4; + + /* h = h % 2^128 as four 32-bit words */ + h0 = ((h0 ) | (h1 << 26)); + h1 = ((h1 >> 6) | (h2 << 20)); + h2 = ((h2 >> 12) | (h3 << 14)); + h3 = ((h3 >> 18) | (h4 << 8)); + + /* tag = (h + pad) % 2^128 */ + f = (uint64_t)h0 + ctx->pad[0]; h0 = (uint32_t)f; + f = (uint64_t)h1 + ctx->pad[1] + (f >> 32); h1 = (uint32_t)f; + f = (uint64_t)h2 + ctx->pad[2] + (f >> 32); h2 = (uint32_t)f; + f = (uint64_t)h3 + ctx->pad[3] + (f >> 32); h3 = (uint32_t)f; + + crypto_store32_le(tag, h0); + crypto_store32_le(tag + 4, h1); + crypto_store32_le(tag + 8, h2); + crypto_store32_le(tag + 12, h3); + + crypto_memzero(ctx, sizeof(*ctx)); +} + +void poly1305_auth(uint8_t *tag, const uint8_t *m, size_t len, + const uint8_t *key) +{ + struct poly1305_ctx ctx; + poly1305_init(&ctx, key); + poly1305_update(&ctx, m, len); + poly1305_final(&ctx, tag); +} + +static const uint8_t aead_zero[16] = { 0 }; + +/* Poly1305 over pad16(aad) || pad16(ct) || le64(aad_len) || le64(ct_len), + * keyed with ChaCha20 block 0. */ +static void aead_chacha_tag(const uint8_t *key, const uint8_t *nonce, + const uint8_t *aad, size_t aad_len, + const uint8_t *ct, size_t ct_len, uint8_t *tag) +{ + struct poly1305_ctx p; + uint8_t otk[64]; + uint8_t lens[16]; + + chacha20_block(key, nonce, 0, otk); + poly1305_init(&p, otk); + poly1305_update(&p, aad, aad_len); + if (aad_len & 15) + poly1305_update(&p, aead_zero, 16 - (aad_len & 15)); + poly1305_update(&p, ct, ct_len); + if (ct_len & 15) + poly1305_update(&p, aead_zero, 16 - (ct_len & 15)); + crypto_store64_le(lens, (uint64_t)aad_len); + crypto_store64_le(lens + 8, (uint64_t)ct_len); + poly1305_update(&p, lens, 16); + poly1305_final(&p, tag); + + crypto_memzero(otk, sizeof(otk)); +} + +int aead_encrypt(enum aead_alg alg, + const uint8_t *key, size_t key_len, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *pt, size_t pt_len, + uint8_t *ct, uint8_t *tag, size_t tag_len) +{ + if (key_len != AEAD_KEY_SIZE || nonce_len != AEAD_NONCE_SIZE + || tag_len != AEAD_TAG_SIZE) + return -1; + + switch (alg) + { + case AEAD_CHACHA20_POLY1305: + chacha20_xor(key, nonce, 1, pt, ct, pt_len); + aead_chacha_tag(key, nonce, aad, aad_len, ct, pt_len, tag); + return 0; + case AEAD_AES256_GCM: + { + struct aes_gcm_ctx g; + int ret = aes_gcm_init(&g, key, key_len); + if (ret == 0) + ret = aes_gcm_encrypt(&g, nonce, nonce_len, aad, aad_len, + pt, pt_len, ct, tag); + crypto_memzero(&g, sizeof(g)); + return ret; + } + default: + break; + } + return -1; +} + +int aead_decrypt(enum aead_alg alg, + const uint8_t *key, size_t key_len, + const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, + const uint8_t *ct, size_t ct_len, + const uint8_t *tag, size_t tag_len, + uint8_t *pt) +{ + if (key_len != AEAD_KEY_SIZE || nonce_len != AEAD_NONCE_SIZE + || tag_len != AEAD_TAG_SIZE) + return -1; + + switch (alg) + { + case AEAD_CHACHA20_POLY1305: + { + uint8_t calc[16]; + int ok; + aead_chacha_tag(key, nonce, aad, aad_len, ct, ct_len, calc); + ok = crypto_memeq_ct(calc, tag, 16); + crypto_memzero(calc, sizeof(calc)); + if (!ok) + return -2; + chacha20_xor(key, nonce, 1, ct, pt, ct_len); + return 0; + } + case AEAD_AES256_GCM: + { + struct aes_gcm_ctx g; + int ret = aes_gcm_init(&g, key, key_len); + if (ret == 0) + ret = aes_gcm_decrypt(&g, nonce, nonce_len, aad, aad_len, + ct, ct_len, tag, pt) ? -2 : 0; + crypto_memzero(&g, sizeof(g)); + return ret; + } + default: + break; + } + return -1; +} + diff --git a/dependencies/libretro-common/crypto/kdf.c b/dependencies/libretro-common/crypto/kdf.c new file mode 100644 index 00000000..49f4f8c6 --- /dev/null +++ b/dependencies/libretro-common/crypto/kdf.c @@ -0,0 +1,914 @@ +/* Copyright (C) 2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (kdf.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +void hmac_sha256_init(struct hmac_sha256_ctx *ctx, + const uint8_t *key, size_t key_len) +{ + unsigned i; + uint8_t k[64]; + + memset(k, 0, sizeof(k)); + if (key_len > 64) + { + struct sha256_state s; + sha256_stream_init(&s, 0); + sha256_stream_update(&s, key, key_len); + sha256_stream_final(&s, k); + crypto_memzero(&s, sizeof(s)); + } + else + memcpy(k, key, key_len); + + for (i = 0; i < 64; i++) + k[i] ^= 0x36; + sha256_stream_init(&ctx->inner, 0); + sha256_stream_update(&ctx->inner, k, 64); + + for (i = 0; i < 64; i++) + k[i] ^= 0x36 ^ 0x5c; + sha256_stream_init(&ctx->outer, 0); + sha256_stream_update(&ctx->outer, k, 64); + + crypto_memzero(k, sizeof(k)); +} + +void hmac_sha256_update(struct hmac_sha256_ctx *ctx, + const uint8_t *data, size_t len) +{ + sha256_stream_update(&ctx->inner, data, len); +} + +void hmac_sha256_final(struct hmac_sha256_ctx *ctx, uint8_t *mac) +{ + uint8_t ih[32]; + sha256_stream_final(&ctx->inner, ih); + sha256_stream_update(&ctx->outer, ih, 32); + sha256_stream_final(&ctx->outer, mac); + crypto_memzero(ih, sizeof(ih)); + crypto_memzero(ctx, sizeof(*ctx)); +} + +void hmac_sha256(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t len, uint8_t *mac) +{ + struct hmac_sha256_ctx ctx; + hmac_sha256_init(&ctx, key, key_len); + hmac_sha256_update(&ctx, data, len); + hmac_sha256_final(&ctx, mac); +} + +/* HMAC-SHA1: what Kerberos aes*-cts-hmac-sha1-96 (RFC 3962) keys its + * checksums and its string-to-key PBKDF2 with. Same shape as the + * SHA-256 one above. */ +void hmac_sha1_init(struct hmac_sha1_ctx *ctx, + const uint8_t *key, size_t key_len) +{ + unsigned i; + uint8_t k[64]; + + memset(k, 0, sizeof(k)); + if (key_len > 64) + SHA1Digest(key, key_len, k); + else + memcpy(k, key, key_len); + + for (i = 0; i < 64; i++) + k[i] ^= 0x36; + sha1_stream_init(&ctx->inner); + sha1_stream_update(&ctx->inner, k, 64); + + for (i = 0; i < 64; i++) + k[i] ^= 0x36 ^ 0x5c; + sha1_stream_init(&ctx->outer); + sha1_stream_update(&ctx->outer, k, 64); + + crypto_memzero(k, sizeof(k)); +} + +void hmac_sha1_update(struct hmac_sha1_ctx *ctx, + const uint8_t *data, size_t len) +{ + sha1_stream_update(&ctx->inner, data, len); +} + +void hmac_sha1_final(struct hmac_sha1_ctx *ctx, uint8_t *mac) +{ + uint8_t ih[20]; + sha1_stream_final(&ctx->inner, ih); + sha1_stream_update(&ctx->outer, ih, 20); + sha1_stream_final(&ctx->outer, mac); + crypto_memzero(ih, sizeof(ih)); + crypto_memzero(ctx, sizeof(*ctx)); +} + +void hmac_sha1(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t len, uint8_t *mac) +{ + struct hmac_sha1_ctx ctx; + hmac_sha1_init(&ctx, key, key_len); + hmac_sha1_update(&ctx, data, len); + hmac_sha1_final(&ctx, mac); +} + +int pbkdf2_hmac_sha1(const uint8_t *password, size_t password_len, + const uint8_t *salt, size_t salt_len, uint32_t iterations, + uint8_t *out, size_t out_len) +{ + /* as pbkdf2_hmac_sha256: keyed once, state copied per block */ + struct pbkdf2_sha1_work + { + struct hmac_sha1_ctx base; + struct hmac_sha1_ctx ctx; + } *w; + uint8_t u[20]; + uint8_t t[20]; + uint8_t ibe[4]; + uint32_t block = 0; + size_t done = 0; + + if (!iterations) + return -1; + if (!(w = (struct pbkdf2_sha1_work*)malloc(sizeof(*w)))) + return -1; + + hmac_sha1_init(&w->base, password, password_len); + + while (done < out_len) + { + uint32_t i; + unsigned j; + size_t take; + + block++; + crypto_store32_be(ibe, block); + + memcpy(&w->ctx, &w->base, sizeof(w->ctx)); + hmac_sha1_update(&w->ctx, salt, salt_len); + hmac_sha1_update(&w->ctx, ibe, 4); + hmac_sha1_final(&w->ctx, u); + memcpy(t, u, 20); + + for (i = 1; i < iterations; i++) + { + memcpy(&w->ctx, &w->base, sizeof(w->ctx)); + hmac_sha1_update(&w->ctx, u, 20); + hmac_sha1_final(&w->ctx, u); + for (j = 0; j < 20; j++) + t[j] ^= u[j]; + } + + take = out_len - done; + if (take > 20) + take = 20; + memcpy(out + done, t, take); + done += take; + } + + crypto_memzero(w, sizeof(*w)); + free(w); + crypto_memzero(u, sizeof(u)); + crypto_memzero(t, sizeof(t)); + return 0; +} + +void hmac_sha384_init(struct hmac_sha384_ctx *ctx, + const uint8_t *key, size_t key_len) +{ + unsigned i; + uint8_t k[128]; + + memset(k, 0, sizeof(k)); + if (key_len > 128) + { + struct sha512_state s; + sha512_stream_init(&s, 1); + sha512_stream_update(&s, key, key_len); + sha512_stream_final(&s, k); + } + else + memcpy(k, key, key_len); + + for (i = 0; i < 128; i++) + k[i] ^= 0x36; + sha512_stream_init(&ctx->inner, 1); + sha512_stream_update(&ctx->inner, k, 128); + + for (i = 0; i < 128; i++) + k[i] ^= 0x36 ^ 0x5c; + sha512_stream_init(&ctx->outer, 1); + sha512_stream_update(&ctx->outer, k, 128); + + crypto_memzero(k, sizeof(k)); +} + +void hmac_sha384_update(struct hmac_sha384_ctx *ctx, + const uint8_t *data, size_t len) +{ + sha512_stream_update(&ctx->inner, data, len); +} + +void hmac_sha384_final(struct hmac_sha384_ctx *ctx, uint8_t *mac) +{ + uint8_t ih[48]; + sha512_stream_final(&ctx->inner, ih); + sha512_stream_update(&ctx->outer, ih, 48); + sha512_stream_final(&ctx->outer, mac); + crypto_memzero(ih, sizeof(ih)); + crypto_memzero(ctx, sizeof(*ctx)); +} + +void hkdf_sha256_extract(const uint8_t *salt, size_t salt_len, + const uint8_t *ikm, size_t ikm_len, uint8_t *prk) +{ + static const uint8_t zero[32] = { 0 }; + if (!salt || !salt_len) + { + salt = zero; + salt_len = sizeof(zero); + } + hmac_sha256(salt, salt_len, ikm, ikm_len, prk); +} + +int hkdf_sha256_expand(const uint8_t *prk, size_t prk_len, + const uint8_t *info, size_t info_len, + uint8_t *okm, size_t okm_len) +{ + struct hmac_sha256_ctx ctx; + uint8_t t[32]; + size_t tlen = 0; + size_t done = 0; + uint8_t n = 0; + + if (okm_len > 255 * 32) + return -1; + + while (done < okm_len) + { + size_t take; + n++; + hmac_sha256_init(&ctx, prk, prk_len); + hmac_sha256_update(&ctx, t, tlen); + hmac_sha256_update(&ctx, info, info_len); + hmac_sha256_update(&ctx, &n, 1); + hmac_sha256_final(&ctx, t); + tlen = 32; + + take = okm_len - done; + if (take > 32) + take = 32; + memcpy(okm + done, t, take); + done += take; + } + + crypto_memzero(t, sizeof(t)); + return 0; +} + +void hmac_sha384(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t len, uint8_t *mac) +{ + struct hmac_sha384_ctx ctx; + hmac_sha384_init(&ctx, key, key_len); + hmac_sha384_update(&ctx, data, len); + hmac_sha384_final(&ctx, mac); +} + +/* HKDF over SHA-384, for the TLS 1.3 SHA-384 suite. */ +void hkdf_sha384_extract(const uint8_t *salt, size_t salt_len, + const uint8_t *ikm, size_t ikm_len, uint8_t *prk) +{ + static const uint8_t zero[48] = { 0 }; + if (!salt || !salt_len) + { + salt = zero; + salt_len = sizeof(zero); + } + hmac_sha384(salt, salt_len, ikm, ikm_len, prk); +} + +int hkdf_sha384_expand(const uint8_t *prk, size_t prk_len, + const uint8_t *info, size_t info_len, + uint8_t *okm, size_t okm_len) +{ + struct hmac_sha384_ctx ctx; + uint8_t t[48]; + size_t tlen = 0; + size_t done = 0; + uint8_t n = 0; + + if (okm_len > 255 * 48) + return -1; + + while (done < okm_len) + { + size_t take; + n++; + hmac_sha384_init(&ctx, prk, prk_len); + hmac_sha384_update(&ctx, t, tlen); + hmac_sha384_update(&ctx, info, info_len); + hmac_sha384_update(&ctx, &n, 1); + hmac_sha384_final(&ctx, t); + tlen = 48; + + take = okm_len - done; + if (take > 48) + take = 48; + memcpy(okm + done, t, take); + done += take; + } + + crypto_memzero(t, sizeof(t)); + return 0; +} + +int hkdf_sha256(const uint8_t *salt, size_t salt_len, + const uint8_t *ikm, size_t ikm_len, + const uint8_t *info, size_t info_len, + uint8_t *okm, size_t okm_len) +{ + uint8_t prk[32]; + int ret; + hkdf_sha256_extract(salt, salt_len, ikm, ikm_len, prk); + ret = hkdf_sha256_expand(prk, sizeof(prk), info, info_len, okm, okm_len); + crypto_memzero(prk, sizeof(prk)); + return ret; +} + +int pbkdf2_hmac_sha256(const uint8_t *password, size_t password_len, + const uint8_t *salt, size_t salt_len, uint32_t iterations, + uint8_t *out, size_t out_len) +{ + /* The password-keyed HMAC is the same for every block and every + * iteration; key it once and copy the keyed state. Two HMAC + * contexts are 1.5 KiB, most of a small console thread stack, so + * they live on the heap for the duration - a single allocation + * against thousands of compression rounds. */ + struct pbkdf2_work + { + struct hmac_sha256_ctx base; + struct hmac_sha256_ctx ctx; + } *w; + uint8_t u[32]; + uint8_t t[32]; + uint8_t ibe[4]; + uint32_t block = 0; + size_t done = 0; + + if (!iterations) + return -1; + if (!(w = (struct pbkdf2_work*)malloc(sizeof(*w)))) + return -1; + + hmac_sha256_init(&w->base, password, password_len); + + while (done < out_len) + { + uint32_t i; + unsigned j; + size_t take; + + block++; + crypto_store32_be(ibe, block); + + memcpy(&w->ctx, &w->base, sizeof(w->ctx)); + hmac_sha256_update(&w->ctx, salt, salt_len); + hmac_sha256_update(&w->ctx, ibe, 4); + hmac_sha256_final(&w->ctx, u); + memcpy(t, u, 32); + + for (i = 1; i < iterations; i++) + { + memcpy(&w->ctx, &w->base, sizeof(w->ctx)); + hmac_sha256_update(&w->ctx, u, 32); + hmac_sha256_final(&w->ctx, u); + for (j = 0; j < 32; j++) + t[j] ^= u[j]; + } + + take = out_len - done; + if (take > 32) + take = 32; + memcpy(out + done, t, take); + done += take; + } + + crypto_memzero(w, sizeof(*w)); + free(w); + crypto_memzero(u, sizeof(u)); + crypto_memzero(t, sizeof(t)); + return 0; +} + +#if defined(_XBOX) +#include +#elif defined(_WIN32) && !defined(__WINRT__) +#include +#elif defined(__WINRT__) +#include +#include +#elif defined(_3DS) +#include <3ds/types.h> +#include <3ds/services/ps.h> +#elif defined(VITA) +#include +#elif defined(HAVE_LIBNX) +#include +#elif defined(PSP) || defined(WIIU) || defined(PS2) || defined(GEKKO) || defined(__PS3__) || defined(__PSL1GHT__) +#include +#else +#include +#include +#include +#if defined(__linux__) +#include +/* Hidden by unistd.h under -ansi; same declaration features_cpu.c + * carries for the same reason. */ +extern long syscall(long number, ...); +#endif +#endif + +#if defined(_WIN32) && !defined(__WINRT__) && !defined(_XBOX) +/* Both are advapi32 exports; resolved at run time so that neither the + * link line nor the oldest SDK the tree targets (_WIN32_WINNT=0x0400) + * has to know about them. RtlGenRandom (XP and later) first, the + * CryptoAPI provider as the fallback for NT4 / 9x era hosts. */ +typedef BOOLEAN (WINAPI *rtl_gen_random_t)(PVOID, ULONG); +typedef BOOL (WINAPI *crypt_acquire_context_t)(void**, const char*, const char*, DWORD, DWORD); +typedef BOOL (WINAPI *crypt_gen_random_t)(void*, DWORD, BYTE*); +typedef BOOL (WINAPI *crypt_release_context_t)(void*, DWORD); + +#define CRYPTO_PROV_RSA_FULL 1 +#define CRYPTO_CRYPT_VERIFYCONTEXT 0xF0000000 +#define CRYPTO_CRYPT_SILENT 0x00000040 + +static int crypto_random_win32(uint8_t *buf, size_t len) +{ + HMODULE advapi = GetModuleHandleA("advapi32.dll"); + rtl_gen_random_t gen; + crypt_acquire_context_t acquire; + crypt_gen_random_t cgen; + crypt_release_context_t release; + void *prov = NULL; + int ret = -1; + + if (!advapi) + advapi = LoadLibraryA("advapi32.dll"); + if (!advapi) + return -1; + + gen = (rtl_gen_random_t)GetProcAddress(advapi, "SystemFunction036"); + if (gen) + { + /* ULONG per call; loop for the pathological caller. */ + while (len) + { + ULONG chunk = (len > 0x7fffffff) ? 0x7fffffff : (ULONG)len; + if (!gen(buf, chunk)) + break; + buf += chunk; + len -= chunk; + } + if (!len) + return 0; + } + + acquire = (crypt_acquire_context_t)GetProcAddress(advapi, "CryptAcquireContextA"); + cgen = (crypt_gen_random_t)GetProcAddress(advapi, "CryptGenRandom"); + release = (crypt_release_context_t)GetProcAddress(advapi, "CryptReleaseContext"); + if (!acquire || !cgen || !release) + return -1; + + if (!acquire(&prov, NULL, NULL, CRYPTO_PROV_RSA_FULL, + CRYPTO_CRYPT_VERIFYCONTEXT | CRYPTO_CRYPT_SILENT)) + return -1; + if (cgen(prov, (DWORD)len, buf)) + ret = 0; + release(prov, 0); + return ret; +} +#endif + +int crypto_random_bytes(uint8_t *buf, size_t len) +{ + if (!len) + return 0; + +#if defined(_WIN32) && !defined(__WINRT__) && !defined(_XBOX) + return crypto_random_win32(buf, len); +#elif defined(__WINRT__) + return (BCryptGenRandom(NULL, buf, (ULONG)len, + BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0) ? 0 : -1; +#elif defined(_3DS) + PS_GenerateRandomBytes(buf, len); + return 0; +#elif defined(VITA) + while (len) + { + size_t chunk = (len > 64) ? 64 : len; + if (sceKernelGetRandomNumber(buf, chunk) < 0) + return -1; + buf += chunk; + len -= chunk; + } + return 0; +#elif defined(HAVE_LIBNX) + randomGet(buf, len); + return 0; +#elif defined(PSP) || defined(WIIU) || defined(PS2) || defined(GEKKO) || defined(__PS3__) || defined(__PSL1GHT__) || defined(_XBOX) + /* No kernel RNG on these; stir the clock, a call counter and two + * stack/heap addresses through HKDF. Weak, and the only thing + * available. The keychain does not rest on it: its nonces only + * have to be unique per install, which a counter-mixed clock is. */ + { + static uint32_t calls = 0; + uint8_t seed[32]; + uint64_t t; +#if defined(_XBOX) + t = (uint64_t)GetTickCount(); +#else + t = (uint64_t)cpu_features_get_time_usec(); +#endif + calls++; + crypto_store64_le(seed, t); + crypto_store32_le(seed + 8, calls); + crypto_store64_le(seed + 12, (uint64_t)(uintptr_t)buf); + crypto_store64_le(seed + 20, (uint64_t)(uintptr_t)&t); + crypto_store32_le(seed + 28, (uint32_t)len); + return hkdf_sha256(NULL, 0, seed, sizeof(seed), + (const uint8_t*)"rng", 3, buf, len); + } +#else + { + size_t off = 0; +#if defined(__linux__) && defined(SYS_getrandom) + /* getrandom() first: works in a chroot or sandbox with no + * /dev. EINTR is retried, anything else falls through. */ + while (off < len) + { + long r = syscall(SYS_getrandom, buf + off, len - off, 0); + if (r < 0) + { + if (errno == EINTR) + continue; + break; + } + off += (size_t)r; + } + if (off == len) + return 0; + off = 0; +#endif + { + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + while (off < len) + { + ssize_t r = read(fd, buf + off, len - off); + if (r < 0) + { + if (errno == EINTR) + continue; + break; + } + if (r == 0) + break; + off += (size_t)r; + } + close(fd); + return (off == len) ? 0 : -1; + } + } +#endif +} + +static int drbg_mix(struct drbg_ctx *ctx, const uint8_t *fresh, size_t fresh_len, + const uint8_t *extra, size_t extra_len) +{ + /* new key || nonce = HKDF(salt = old key, ikm = fresh || extra) */ + uint8_t prk[32]; + uint8_t okm[DRBG_KEY_SIZE + DRBG_NONCE_SIZE]; + struct hmac_sha256_ctx h; + + hmac_sha256_init(&h, ctx->key, sizeof(ctx->key)); + hmac_sha256_update(&h, fresh, fresh_len); + if (extra && extra_len) + hmac_sha256_update(&h, extra, extra_len); + hmac_sha256_final(&h, prk); + + if (hkdf_sha256_expand(prk, sizeof(prk), + (const uint8_t*)"drbg", 4, okm, sizeof(okm)) != 0) + return -1; + + memcpy(ctx->key, okm, DRBG_KEY_SIZE); + memcpy(ctx->nonce, okm + DRBG_KEY_SIZE, DRBG_NONCE_SIZE); + ctx->counter = 0; + ctx->since_reseed = 0; + ctx->seeded = 1; + + crypto_memzero(prk, sizeof(prk)); + crypto_memzero(okm, sizeof(okm)); + return 0; +} + +int drbg_reseed(struct drbg_ctx *ctx, const uint8_t *extra, size_t extra_len) +{ + uint8_t fresh[48]; + int ret; + if (crypto_random_bytes(fresh, sizeof(fresh)) != 0) + return -1; + ret = drbg_mix(ctx, fresh, sizeof(fresh), extra, extra_len); + crypto_memzero(fresh, sizeof(fresh)); + return ret; +} + +int drbg_init(struct drbg_ctx *ctx, const uint8_t *seed, size_t seed_len) +{ + memset(ctx, 0, sizeof(*ctx)); + if (seed) + return drbg_mix(ctx, seed, seed_len, NULL, 0); + return drbg_reseed(ctx, NULL, 0); +} + +int drbg_generate(struct drbg_ctx *ctx, uint8_t *out, size_t len) +{ + uint8_t block[CHACHA20_BLOCK_SIZE]; + + if (!ctx->seeded) + return -1; + if (ctx->since_reseed + len > DRBG_RESEED_INTERVAL) + if (drbg_reseed(ctx, NULL, 0) != 0) + return -1; + + ctx->since_reseed += len; + + /* First block of every call is the next key (fast key erasure), + * output starts at the block after it. */ + chacha20_block(ctx->key, ctx->nonce, ctx->counter, block); + ctx->counter++; + memcpy(ctx->key, block, DRBG_KEY_SIZE); + + while (len) + { + size_t n = (len < CHACHA20_BLOCK_SIZE) ? len : CHACHA20_BLOCK_SIZE; + chacha20_block(ctx->key, ctx->nonce, ctx->counter, block); + ctx->counter++; + memcpy(out, block, n); + out += n; + len -= n; + } + + /* Ratchet again so the key that produced this output is gone. */ + chacha20_block(ctx->key, ctx->nonce, ctx->counter, block); + ctx->counter++; + memcpy(ctx->key, block, DRBG_KEY_SIZE); + + crypto_memzero(block, sizeof(block)); + return 0; +} + +void drbg_free(struct drbg_ctx *ctx) +{ + crypto_memzero(ctx, sizeof(*ctx)); +} + + +/* ---- MD4 (RFC 1320) ---------------------------------------------- */ + +#define MD4_F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define MD4_G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define MD4_H(x, y, z) ((x) ^ (y) ^ (z)) + +static void md4_block(uint32_t *st, const uint8_t *p) +{ + uint32_t x[16]; + uint32_t a = st[0], b = st[1], c = st[2], d = st[3]; + unsigned i; + for (i = 0; i < 16; i++) + x[i] = crypto_load32_le(p + 4 * i); + +#define R1(a, b, c, d, k, s) a = crypto_rotl32(a + MD4_F(b, c, d) + x[k], s) +#define R2(a, b, c, d, k, s) a = crypto_rotl32(a + MD4_G(b, c, d) + x[k] + 0x5a827999u, s) +#define R3(a, b, c, d, k, s) a = crypto_rotl32(a + MD4_H(b, c, d) + x[k] + 0x6ed9eba1u, s) + R1(a,b,c,d, 0,3); R1(d,a,b,c, 1,7); R1(c,d,a,b, 2,11); R1(b,c,d,a, 3,19); + R1(a,b,c,d, 4,3); R1(d,a,b,c, 5,7); R1(c,d,a,b, 6,11); R1(b,c,d,a, 7,19); + R1(a,b,c,d, 8,3); R1(d,a,b,c, 9,7); R1(c,d,a,b,10,11); R1(b,c,d,a,11,19); + R1(a,b,c,d,12,3); R1(d,a,b,c,13,7); R1(c,d,a,b,14,11); R1(b,c,d,a,15,19); + R2(a,b,c,d, 0,3); R2(d,a,b,c, 4,5); R2(c,d,a,b, 8,9); R2(b,c,d,a,12,13); + R2(a,b,c,d, 1,3); R2(d,a,b,c, 5,5); R2(c,d,a,b, 9,9); R2(b,c,d,a,13,13); + R2(a,b,c,d, 2,3); R2(d,a,b,c, 6,5); R2(c,d,a,b,10,9); R2(b,c,d,a,14,13); + R2(a,b,c,d, 3,3); R2(d,a,b,c, 7,5); R2(c,d,a,b,11,9); R2(b,c,d,a,15,13); + R3(a,b,c,d, 0,3); R3(d,a,b,c, 8,9); R3(c,d,a,b, 4,11); R3(b,c,d,a,12,15); + R3(a,b,c,d, 2,3); R3(d,a,b,c,10,9); R3(c,d,a,b, 6,11); R3(b,c,d,a,14,15); + R3(a,b,c,d, 1,3); R3(d,a,b,c, 9,9); R3(c,d,a,b, 5,11); R3(b,c,d,a,13,15); + R3(a,b,c,d, 3,3); R3(d,a,b,c,11,9); R3(c,d,a,b, 7,11); R3(b,c,d,a,15,15); +#undef R1 +#undef R2 +#undef R3 + st[0] += a; st[1] += b; st[2] += c; st[3] += d; + crypto_memzero(x, sizeof(x)); +} + +void md4(const uint8_t *data, size_t len, uint8_t *digest) +{ + uint32_t st[4]; + uint8_t tail[128]; + size_t rem, off = 0, i; + uint64_t bits = (uint64_t)len * 8; + + st[0] = 0x67452301u; st[1] = 0xefcdab89u; + st[2] = 0x98badcfeu; st[3] = 0x10325476u; + + while (len - off >= 64) + { + md4_block(st, data + off); + off += 64; + } + rem = len - off; + memset(tail, 0, sizeof(tail)); + memcpy(tail, data + off, rem); + tail[rem] = 0x80; + i = (rem < 56) ? 56 : 120; + crypto_store64_le(tail + i, bits); + md4_block(st, tail); + if (i == 120) + md4_block(st, tail + 64); + + for (i = 0; i < 4; i++) + crypto_store32_le(digest + 4 * i, st[i]); + crypto_memzero(tail, sizeof(tail)); + crypto_memzero(st, sizeof(st)); +} + +/* ---- HMAC-MD5 ----------------------------------------------------- */ + +void hmac_md5(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t len, uint8_t *mac) +{ + MD5_CTX ctx; + uint8_t k[64]; + uint8_t ih[16]; + unsigned i; + + memset(k, 0, sizeof(k)); + if (key_len > 64) + { + MD5_Init(&ctx); + MD5_Update(&ctx, key, (unsigned long)key_len); + MD5_Final(k, &ctx); + } + else + memcpy(k, key, key_len); + + for (i = 0; i < 64; i++) + k[i] ^= 0x36; + MD5_Init(&ctx); + MD5_Update(&ctx, k, 64); + MD5_Update(&ctx, data, (unsigned long)len); + MD5_Final(ih, &ctx); + + for (i = 0; i < 64; i++) + k[i] ^= 0x36 ^ 0x5c; + MD5_Init(&ctx); + MD5_Update(&ctx, k, 64); + MD5_Update(&ctx, ih, 16); + MD5_Final(mac, &ctx); + + crypto_memzero(k, sizeof(k)); + crypto_memzero(ih, sizeof(ih)); + crypto_memzero(&ctx, sizeof(ctx)); +} + +/* ---- SP 800-108 counter mode ------------------------------------- */ + +void kbkdf_hmac_sha256(const uint8_t *ki, size_t ki_len, + const uint8_t *label, size_t label_len, + const uint8_t *context, size_t context_len, + uint8_t *out, size_t out_len) +{ + struct hmac_sha256_ctx h; + uint8_t be[4]; + uint8_t block[32]; + uint32_t i = 0; + size_t done = 0; + const uint8_t zero = 0; + + while (done < out_len) + { + size_t take = out_len - done < 32 ? out_len - done : 32; + i++; + hmac_sha256_init(&h, ki, ki_len); + crypto_store32_be(be, i); + hmac_sha256_update(&h, be, 4); + hmac_sha256_update(&h, label, label_len); + hmac_sha256_update(&h, &zero, 1); + hmac_sha256_update(&h, context, context_len); + crypto_store32_be(be, (uint32_t)(out_len * 8)); + hmac_sha256_update(&h, be, 4); + hmac_sha256_final(&h, block); + memcpy(out + done, block, take); + done += take; + } + crypto_memzero(block, sizeof(block)); +} + +/* ---- NTOWFv2 ------------------------------------------------------ */ + +/* UTF-8 to UTF-16LE into a caller buffer of @cap code units; + * returns the octet count or 0 when it does not fit / is not UTF-8. */ +static void crypto_store16_le_pair(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static size_t ntlm_utf16le(const char *s, uint8_t *out, size_t cap, + int upper) +{ + size_t n = 0; + while (*s) + { + uint32_t cp = utf8_walk(&s); + if (cp == 0 || cp > 0x10ffff) + return 0; + if (upper && cp >= 'a' && cp <= 'z') + cp -= 32; + if (cp >= 0x10000) + { + if (n + 2 > cap) + return 0; + cp -= 0x10000; + crypto_store16_le_pair(out + 2 * n, (uint16_t)(0xd800 | (cp >> 10))); + crypto_store16_le_pair(out + 2 * n + 2, (uint16_t)(0xdc00 | (cp & 0x3ff))); + n += 2; + } + else + { + if (n + 1 > cap) + return 0; + crypto_store16_le_pair(out + 2 * n, (uint16_t)cp); + n += 1; + } + } + return n * 2; +} + +int ntlm_ntowf_v2(const char *password, const char *user, + const char *domain, uint8_t *out) +{ + /* 255 code points each, as UTF-16 with surrogates: 1020 octets. */ + uint8_t *buf = (uint8_t*)malloc(2 * 1020); + uint8_t nt[16]; + size_t plen, ulen, dlen; + if (!buf) + return -1; + + plen = ntlm_utf16le(password, buf, 510, 0); + if (!plen && *password) + { + free(buf); + return -1; + } + md4(buf, plen, nt); + + ulen = ntlm_utf16le(user, buf, 510, 1); + dlen = ntlm_utf16le(domain, buf + ulen, 510, 0); + if ((!ulen && *user) || (!dlen && *domain)) + { + crypto_memzero(buf, 2 * 1020); + free(buf); + return -1; + } + hmac_md5(nt, 16, buf, ulen + dlen, out); + crypto_memzero(nt, sizeof(nt)); + crypto_memzero(buf, 2 * 1020); + free(buf); + return 0; +} diff --git a/dependencies/libretro-common/crypto/pk.c b/dependencies/libretro-common/crypto/pk.c new file mode 100644 index 00000000..42afeacd --- /dev/null +++ b/dependencies/libretro-common/crypto/pk.c @@ -0,0 +1,2950 @@ +/* Copyright (C) 2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (pk.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +void bn_from_be(bn_word *r, unsigned k, const uint8_t *in, size_t len) +{ + unsigned i; + size_t pos = len; + + for (i = 0; i < k; i++) + { + bn_word w = 0; + unsigned b; + for (b = 0; b < BN_WORD_BYTES && pos > 0; b++) + { + pos--; + w |= (bn_word)in[pos] << (8 * b); + } + r[i] = w; + } +} + +void bn_to_be(uint8_t *out, size_t len, const bn_word *a, unsigned k) +{ + size_t pos = len; + unsigned i; + + for (i = 0; i < k && pos > 0; i++) + { + unsigned b; + for (b = 0; b < BN_WORD_BYTES && pos > 0; b++) + { + pos--; + out[pos] = (uint8_t)(a[i] >> (8 * b)); + } + } + while (pos > 0) + out[--pos] = 0; +} + +void bn_zero(bn_word *r, unsigned k) +{ + memset(r, 0, k * sizeof(bn_word)); +} + +void bn_copy(bn_word *r, const bn_word *a, unsigned k) +{ + memcpy(r, a, k * sizeof(bn_word)); +} + +int bn_is_zero(const bn_word *a, unsigned k) +{ + bn_word acc = 0; + unsigned i; + for (i = 0; i < k; i++) + acc |= a[i]; + return acc == 0; +} + +int bn_cmp(const bn_word *a, const bn_word *b, unsigned k) +{ + /* Walk from the top; keep the first difference only. */ + bn_word gt = 0, lt = 0; + unsigned i; + for (i = k; i-- > 0; ) + { + bn_word g = (bn_word)(a[i] > b[i]); + bn_word l = (bn_word)(a[i] < b[i]); + bn_word undecided = (bn_word)((gt | lt) == 0); + gt |= g & undecided; + lt |= l & undecided; + } + return (int)gt - (int)lt; +} + +bn_word bn_add(bn_word *r, const bn_word *a, const bn_word *b, unsigned k) +{ + bn_dword carry = 0; + unsigned i; + for (i = 0; i < k; i++) + { + carry += (bn_dword)a[i] + b[i]; + r[i] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + return (bn_word)carry; +} + +bn_word bn_sub(bn_word *r, const bn_word *a, const bn_word *b, unsigned k) +{ + bn_dword borrow = 0; + unsigned i; + for (i = 0; i < k; i++) + { + bn_dword d = (bn_dword)a[i] - b[i] - borrow; + r[i] = (bn_word)d; + borrow = (d >> BN_WORD_BITS) & 1; + } + return (bn_word)borrow; +} + +void bn_select(bn_word *r, bn_word bit, const bn_word *a, + const bn_word *b, unsigned k) +{ + bn_word mask = 0 - (bit & 1); + unsigned i; + for (i = 0; i < k; i++) + r[i] = (a[i] & mask) | (b[i] & ~mask); +} + +void bn_mod_add(bn_word *r, const bn_word *a, const bn_word *b, + const bn_word *m, unsigned k) +{ + bn_word carry = bn_add(r, a, b, k); + /* Subtract m and keep that result when there was a carry out or + * the sum is still >= m. */ + bn_word borrow; + bn_word t[BN_MAX_WORDS]; + borrow = bn_sub(t, r, m, k); + bn_select(r, carry | (bn_word)(borrow == 0), t, r, k); +} + +void bn_mod_sub(bn_word *r, const bn_word *a, const bn_word *b, + const bn_word *m, unsigned k) +{ + bn_word borrow = bn_sub(r, a, b, k); + bn_word t[BN_MAX_WORDS]; + bn_add(t, r, m, k); + bn_select(r, borrow, t, r, k); +} + +unsigned bn_bit_length(const bn_word *a, unsigned k) +{ + unsigned i; + for (i = k; i-- > 0; ) + { + if (a[i]) + { + unsigned bits = 0; + bn_word v = a[i]; + while (v) + { + bits++; + v >>= 1; + } + return i * BN_WORD_BITS + bits; + } + } + return 0; +} + +unsigned bn_get_bit(const bn_word *a, unsigned i) +{ + return (a[i / BN_WORD_BITS] >> (i % BN_WORD_BITS)) & 1; +} + +bn_word bn_mont_n0(const bn_word *m) +{ + /* -m^-1 mod 2^BN_WORD_BITS by Newton iteration; m odd. The seed + * m itself is correct to 3 bits and each step doubles that. */ + bn_word x = m[0]; + unsigned i; + for (i = 0; i < 6; i++) + x *= 2 - m[0] * x; + return 0 - x; +} + +void bn_mont_r2(bn_word *r2, const bn_word *m, unsigned k) +{ + /* R^2 = 2^(2 bits) mod m. Doubling all the way is 2 * bits modular + * additions, most of the cost of an RSA verify; instead reach + * x = 2^(bits + 32) mod m by doubling (33 steps when m has its top + * bit set, as every RSA modulus and both P-256 primes do), then + * square in the Montgomery domain: mont_mul(2^(bits + s), same) + * = 2^(bits + 2s), so s doubles per squaring and log2(bits) + * squarings reach 2^(2 bits). Any leftover for a bits count that + * is not a power of two is made up by doubling at the end. */ + bn_word tmp[BN_MONT_TMP_WORDS(BN_MAX_WORDS)]; + bn_word n0 = bn_mont_n0(m); + unsigned bits = BN_WORD_BITS * k; + unsigned s; + unsigned i = 0; + + bn_zero(r2, k); + if (m[k - 1] >> (BN_WORD_BITS - 1)) + { + r2[k - 1] = (bn_word)1 << (BN_WORD_BITS - 1); + i = bits - 1; + } + else + r2[0] = 1; + /* A doubling is a k-word add, a squaring is k^2 products: for the + * sizes here 32 doublings cost about one squaring and save five. */ + for (; i < bits + 32; i++) + bn_mod_add(r2, r2, r2, m, k); + + for (s = 32; 2 * s <= bits; s *= 2) + bn_mont_sqr(r2, r2, m, n0, k, tmp); + for (; s < bits; s++) + bn_mod_add(r2, r2, r2, m, k); +} + +void bn_mont_mul(bn_word *r, const bn_word *a, const bn_word *b, + const bn_word *m, bn_word n0, unsigned k, bn_word *tmp) +{ + /* CIOS: tmp has k + 2 words. */ + unsigned i, j; + memset(tmp, 0, (k + 2) * sizeof(bn_word)); + + for (i = 0; i < k; i++) + { + bn_dword carry = 0; + bn_word u; + for (j = 0; j < k; j++) + { + carry += (bn_dword)tmp[j] + (bn_dword)a[j] * b[i]; + tmp[j] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + carry += tmp[k]; + tmp[k] = (bn_word)carry; + tmp[k + 1] = (bn_word)(carry >> BN_WORD_BITS); + + u = tmp[0] * n0; + carry = (bn_dword)tmp[0] + (bn_dword)u * m[0]; + carry >>= BN_WORD_BITS; + for (j = 1; j < k; j++) + { + carry += (bn_dword)tmp[j] + (bn_dword)u * m[j]; + tmp[j - 1] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + carry += tmp[k]; + tmp[k - 1] = (bn_word)carry; + tmp[k] = tmp[k + 1] + (bn_word)(carry >> BN_WORD_BITS); + } + + /* Result in tmp[0..k], < 2m: one conditional subtraction. */ + { + bn_word borrow = bn_sub(r, tmp, m, k); + /* Keep the subtraction when tmp[k] is set or no borrow. */ + bn_select(r, tmp[k] | (bn_word)(borrow == 0), r, tmp, k); + } +} + +/* Montgomery reduction of the 2k-word product in t, result to r. */ +static void bn_mont_reduce(bn_word *r, bn_word *t, + const bn_word *m, bn_word n0, unsigned k) +{ + unsigned i, j; + bn_word extra = 0; + + for (i = 0; i < k; i++) + { + bn_word u = t[i] * n0; + bn_dword carry = 0; + for (j = 0; j < k; j++) + { + carry += (bn_dword)t[i + j] + (bn_dword)u * m[j]; + t[i + j] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + for (j = i + k; carry && j < 2 * k; j++) + { + carry += t[j]; + t[j] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + extra += (bn_word)carry; + } + + { + bn_word borrow = bn_sub(r, t + k, m, k); + bn_select(r, extra | (bn_word)(borrow == 0), r, t + k, k); + } +} + +void bn_mont_sqr(bn_word *r, const bn_word *a, + const bn_word *m, bn_word n0, unsigned k, bn_word *tmp) +{ + unsigned i, j; + bn_dword carry; + + memset(tmp, 0, 2 * k * sizeof(bn_word)); + + /* Off-diagonal products, each once. */ + for (i = 0; i < k; i++) + { + carry = 0; + for (j = i + 1; j < k; j++) + { + carry += (bn_dword)tmp[i + j] + (bn_dword)a[i] * a[j]; + tmp[i + j] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + tmp[i + k] = (bn_word)carry; + } + + /* Double them, then add the squares on the diagonal. */ + carry = 0; + for (i = 0; i < 2 * k; i++) + { + carry += (bn_dword)tmp[i] << 1; + tmp[i] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + carry = 0; + for (i = 0; i < k; i++) + { + carry += (bn_dword)tmp[2 * i] + (bn_dword)a[i] * a[i]; + tmp[2 * i] = (bn_word)carry; + carry >>= BN_WORD_BITS; + carry += tmp[2 * i + 1]; + tmp[2 * i + 1] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + + bn_mont_reduce(r, tmp, m, n0, k); +} + +void bn_mod_mul(bn_word *r, const bn_word *a, const bn_word *b, + const bn_word *m, const bn_word *r2, bn_word n0, unsigned k, + bn_word *tmp) +{ + /* (a * R) * b * R^-1 = a * b; the intermediate needs its own home, + * and r may alias a or b. */ + bn_word am[BN_MAX_WORDS]; + bn_mont_mul(am, a, r2, m, n0, k, tmp); + bn_mont_mul(r, am, b, m, n0, k, tmp); +} + +void bn_mod_exp(bn_word *r, const bn_word *base, + const bn_word *exp, unsigned ek, + const bn_word *m, const bn_word *r2, bn_word n0, unsigned k, + bn_word *work) +{ + bn_word *acc = work; /* k */ + bn_word *bm = work + k; /* k */ + bn_word *t = work + 2 * k; /* k */ + bn_word *tmp = work + 3 * k; /* k + 2 */ + unsigned bits = bn_bit_length(exp, ek); + unsigned i; + + /* acc = R mod m (Montgomery one) = mont_mul(R^2, 1) */ + bn_zero(t, k); + t[0] = 1; + bn_mont_mul(acc, r2, t, m, n0, k, tmp); + bn_mont_mul(bm, base, r2, m, n0, k, tmp); + + for (i = bits; i-- > 0; ) + { + bn_mont_sqr(t, acc, m, n0, k, tmp); + if (bn_get_bit(exp, i)) + bn_mont_mul(acc, t, bm, m, n0, k, tmp); + else + bn_copy(acc, t, k); + } + + bn_zero(t, k); + t[0] = 1; + bn_mont_mul(r, acc, t, m, n0, k, tmp); +} + +static const uint8_t rsa_di_sha256[19] = { + 0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20 +}; +static const uint8_t rsa_di_sha384[19] = { + 0x30,0x41,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02,0x05,0x00,0x04,0x30 +}; +static const uint8_t rsa_di_sha512[19] = { + 0x30,0x51,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03,0x05,0x00,0x04,0x40 +}; +static const uint8_t rsa_di_sha1[15] = { + 0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14 +}; + +/* The DigestInfo prefix and digest length of @hash; MD5 || SHA-1 (TLS + * 1.0 and 1.1) has none. */ +static int rsa_pkcs1_hash(enum rsa_hash hash, const uint8_t **di, + size_t *di_len, size_t *hlen) +{ + switch (hash) + { + case RSA_HASH_SHA256: *di = rsa_di_sha256; *di_len = 19; *hlen = 32; break; + case RSA_HASH_SHA384: *di = rsa_di_sha384; *di_len = 19; *hlen = 48; break; + case RSA_HASH_SHA512: *di = rsa_di_sha512; *di_len = 19; *hlen = 64; break; + case RSA_HASH_SHA1: *di = rsa_di_sha1; *di_len = 15; *hlen = 20; break; + case RSA_HASH_MD5_SHA1: *di = NULL; *di_len = 0; *hlen = 36; break; + default: + return -1; + } + return 0; +} + +/* EM = 0x00 || 0x01 || PS (0xff, at least 8) || 0x00 || T, @n_len octets; + * the caller has checked that T fits. */ +static void rsa_pkcs1_encode(uint8_t *em, size_t n_len, const uint8_t *di, + size_t di_len, const uint8_t *digest, size_t hlen) +{ + size_t ps_len = n_len - di_len - hlen - 3; + em[0] = 0x00; + em[1] = 0x01; + memset(em + 2, 0xff, ps_len); + em[2 + ps_len] = 0x00; + if (di_len) + memcpy(em + 3 + ps_len, di, di_len); + memcpy(em + 3 + ps_len + di_len, digest, hlen); +} + +struct rsa_work +{ + bn_word mod[BN_MAX_WORDS]; + bn_word r2[BN_MAX_WORDS]; + bn_word s[BN_MAX_WORDS]; + bn_word exp[BN_MAX_WORDS]; + bn_word m[BN_MAX_WORDS]; + bn_word work[3 * BN_MAX_WORDS + BN_MONT_TMP_WORDS(BN_MAX_WORDS)]; + uint8_t em[BN_MAX_WORDS * BN_WORD_BYTES]; + uint8_t expect[BN_MAX_WORDS * BN_WORD_BYTES]; +}; + +int rsa_pkcs1_verify(const uint8_t *n, size_t n_len, + const uint8_t *e, size_t e_len, + enum rsa_hash hash, const uint8_t *digest, size_t digest_len, + const uint8_t *sig, size_t sig_len) +{ + const uint8_t *di; + size_t di_len; + size_t hlen; + size_t tlen; + unsigned k, ek; + struct rsa_work *w; + int ok; + + if (rsa_pkcs1_hash(hash, &di, &di_len, &hlen) != 0 || digest_len != hlen) + return -1; + + while (n_len && n[0] == 0) + { + n++; + n_len--; + } + while (e_len && e[0] == 0) + { + e++; + e_len--; + } + /* 512-bit floor, 4096-bit ceiling, odd modulus and exponent; + * the signature is exactly the modulus length (RFC 8017 8.2.2 + * step 1). */ + if (n_len < 64 || n_len > BN_MAX_WORDS * BN_WORD_BYTES || !(n[n_len - 1] & 1)) + return -1; + if (!e_len || e_len > n_len || !(e[e_len - 1] & 1)) + return -1; + if (sig_len != n_len) + return -1; + tlen = di_len + hlen; + if (n_len < tlen + 11) + return -1; + + if (!(w = (struct rsa_work*)malloc(sizeof(*w)))) + return -1; + + k = (unsigned)((n_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + ek = (unsigned)((e_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + bn_from_be(w->mod, k, n, n_len); + bn_from_be(w->s, k, sig, sig_len); + bn_from_be(w->exp, ek, e, e_len); + + ok = 0; + /* s < n (step 2a of RSAVP1) */ + if (bn_cmp(w->s, w->mod, k) < 0) + { + bn_mont_r2(w->r2, w->mod, k); + bn_mod_exp(w->m, w->s, w->exp, ek, w->mod, w->r2, + bn_mont_n0(w->mod), k, w->work); + bn_to_be(w->em, n_len, w->m, k); + rsa_pkcs1_encode(w->expect, n_len, di, di_len, digest, hlen); + ok = crypto_memeq_ct(w->em, w->expect, n_len); + } + + free(w); + return ok ? 0 : -1; +} + +int rsa_pkcs1_encrypt(const uint8_t *n, size_t n_len, + const uint8_t *e, size_t e_len, + const uint8_t *msg, size_t msg_len, uint8_t *out, size_t out_len) +{ + unsigned k, ek; + size_t ps_len, i; + struct rsa_work *w; + int ret = -1; + + while (n_len && n[0] == 0) + { + n++; + n_len--; + } + while (e_len && e[0] == 0) + { + e++; + e_len--; + } + if (n_len < 64 || n_len > BN_MAX_WORDS * BN_WORD_BYTES || !(n[n_len - 1] & 1)) + return -1; + if (!e_len || e_len > n_len || out_len != n_len || msg_len + 11 > n_len) + return -1; + if (!(w = (struct rsa_work*)malloc(sizeof(*w)))) + return -1; + + /* EM = 0x00 || 0x02 || PS (nonzero random, at least 8) || 0x00 || M */ + ps_len = n_len - msg_len - 3; + w->em[0] = 0x00; + w->em[1] = 0x02; + if (crypto_random_bytes(w->em + 2, ps_len) != 0) + goto done; + for (i = 0; i < ps_len; i++) + while (!w->em[2 + i]) + if (crypto_random_bytes(w->em + 2 + i, 1) != 0) + goto done; + w->em[2 + ps_len] = 0x00; + memcpy(w->em + 3 + ps_len, msg, msg_len); + + k = (unsigned)((n_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + ek = (unsigned)((e_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + bn_from_be(w->mod, k, n, n_len); + bn_from_be(w->m, k, w->em, n_len); + bn_from_be(w->exp, ek, e, e_len); + bn_mont_r2(w->r2, w->mod, k); + bn_mod_exp(w->s, w->m, w->exp, ek, w->mod, w->r2, + bn_mont_n0(w->mod), k, w->work); + bn_to_be(out, out_len, w->s, k); + ret = 0; + +done: + crypto_memzero(w, sizeof(*w)); + free(w); + return ret; +} + +/* RSASSA-PSS verification (RFC 8017 9.1.2) with MGF1 over the same + * hash and a salt of the hash length, the form TLS 1.3 signs with. + * The modular exponentiation is the one rsa_pkcs1_verify() uses. */ +static void rsa_hash_of(enum rsa_hash hash, const uint8_t *data, size_t len, uint8_t *out) +{ + if (hash == RSA_HASH_SHA384) + { + struct sha512_state st; + sha512_stream_init(&st, 1); + sha512_stream_update(&st, data, len); + sha512_stream_final(&st, out); + } + else + { + struct sha256_state st; + sha256_stream_init(&st, 0); + sha256_stream_update(&st, data, len); + sha256_stream_final(&st, out); + } +} + +int rsa_pss_verify(const uint8_t *n, size_t n_len, + const uint8_t *e, size_t e_len, + enum rsa_hash hash, const uint8_t *digest, size_t digest_len, + const uint8_t *sig, size_t sig_len) +{ + size_t hlen, em_len, db_len, i, counter; + unsigned k, ek; + struct rsa_work *w; + uint8_t *db, *h, *db_mask, hprime[64], seed[64 + 4]; + int ok = 0; + + switch (hash) + { + case RSA_HASH_SHA256: hlen = 32; break; + case RSA_HASH_SHA384: hlen = 48; break; + default: + return -1; + } + if (digest_len != hlen) + return -1; + while (n_len && n[0] == 0) { n++; n_len--; } + while (e_len && e[0] == 0) { e++; e_len--; } + if (n_len < 64 || n_len > BN_MAX_WORDS * BN_WORD_BYTES || !(n[n_len - 1] & 1)) + return -1; + if (!e_len || e_len > n_len || !(e[e_len - 1] & 1)) + return -1; + if (sig_len != n_len) + return -1; + em_len = n_len; /* emBits = modBits - 1; a full octet count when + the top bit of n is set, which the mask below + handles for any modBits */ + if (em_len < 2 * hlen + 2) + return -1; + if (!(w = (struct rsa_work*)malloc(sizeof(*w)))) + return -1; + k = (unsigned)((n_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + ek = (unsigned)((e_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + bn_from_be(w->mod, k, n, n_len); + bn_from_be(w->s, k, sig, sig_len); + bn_from_be(w->exp, ek, e, e_len); + if (bn_cmp(w->s, w->mod, k) >= 0) + goto done; + bn_mont_r2(w->r2, w->mod, k); + bn_mod_exp(w->m, w->s, w->exp, ek, w->mod, w->r2, bn_mont_n0(w->mod), k, w->work); + bn_to_be(w->em, em_len, w->m, k); + + /* EM = maskedDB || H || 0xbc */ + if (w->em[em_len - 1] != 0xbc) + goto done; + db_len = em_len - hlen - 1; + db = w->em; + h = w->em + db_len; + db_mask = w->expect; /* scratch of em_len octets */ + /* MGF1(H, db_len) */ + memcpy(seed, h, hlen); + for (counter = 0, i = 0; i < db_len; counter++) + { + uint8_t t[64]; + size_t take; + seed[hlen] = (uint8_t)(counter >> 24); + seed[hlen + 1] = (uint8_t)(counter >> 16); + seed[hlen + 2] = (uint8_t)(counter >> 8); + seed[hlen + 3] = (uint8_t)counter; + rsa_hash_of(hash, seed, hlen + 4, t); + take = db_len - i < hlen ? db_len - i : hlen; + memcpy(db_mask + i, t, take); + i += take; + } + for (i = 0; i < db_len; i++) + db[i] ^= db_mask[i]; + /* the bits above emBits are cleared: with emBits = modBits - 1 and + * a modulus whose top octet is used, that is the top bit */ + { + unsigned top_bits = 0; + uint8_t t = n[0]; + while (t) { top_bits++; t >>= 1; } + /* emBits = 8 * n_len - (8 - top_bits) - 1 */ + { + unsigned em_bits = 8 * (unsigned)n_len - (8 - top_bits) - 1; + unsigned clear = 8 * (unsigned)em_len - em_bits; + db[0] &= (uint8_t)(0xff >> clear); + } + } + /* DB = PS (zeros) || 0x01 || salt, salt of hlen octets */ + for (i = 0; i < db_len - hlen - 1; i++) + if (db[i] != 0) + goto done; + if (db[db_len - hlen - 1] != 0x01) + goto done; + /* H' = Hash(0x00 * 8 || mHash || salt) */ + { + uint8_t *m2 = w->expect; /* db_mask no longer needed */ + memset(m2, 0, 8); + memcpy(m2 + 8, digest, hlen); + memcpy(m2 + 8 + hlen, db + db_len - hlen, hlen); + rsa_hash_of(hash, m2, 8 + 2 * hlen, hprime); + } + ok = crypto_memeq_ct(h, hprime, hlen); +done: + free(w); + return ok ? 0 : -1; +} + +/* X25519 (RFC 7748): the Montgomery ladder over 2^255 - 19 on the + * same Montgomery-form field arithmetic the curves use. Scalars and + * coordinates are little-endian on the wire; the scalar is clamped as + * the RFC says and the top bit of a coordinate ignored. */ + +#if defined(__SIZEOF_INT128__) && !defined(X25519_NO_128) +/* 64-bit machines: arithmetic mod 2^255 - 19 in five 51-bit limbs, + * products in 128 bits. 2^255 = 19 (mod p), so what a product carries + * past limb 4 folds back into limb 0 times 19. Limbs stay under 2^52 + * between operations: sums of two reduced values, and f + 2p - g for a + * difference, fit what a multiply takes. */ +__extension__ typedef unsigned __int128 fe51_u128; +typedef uint64_t fe51[5]; +#define FE51_MASK (((uint64_t)1 << 51) - 1) + +static void fe51_frombytes(fe51 h, const uint8_t *s) +{ + uint64_t w[4]; + unsigned i; + for (i = 0; i < 4; i++) + w[i] = (uint64_t)s[8 * i] | ((uint64_t)s[8 * i + 1] << 8) + | ((uint64_t)s[8 * i + 2] << 16) | ((uint64_t)s[8 * i + 3] << 24) + | ((uint64_t)s[8 * i + 4] << 32) | ((uint64_t)s[8 * i + 5] << 40) + | ((uint64_t)s[8 * i + 6] << 48) | ((uint64_t)s[8 * i + 7] << 56); + h[0] = w[0] & FE51_MASK; + h[1] = ((w[0] >> 51) | (w[1] << 13)) & FE51_MASK; + h[2] = ((w[1] >> 38) | (w[2] << 26)) & FE51_MASK; + h[3] = ((w[2] >> 25) | (w[3] << 39)) & FE51_MASK; + h[4] = (w[3] >> 12) & FE51_MASK; /* the top bit ignored */ +} + +static void fe51_carry(fe51 h) +{ + uint64_t c; + c = h[0] >> 51; h[0] &= FE51_MASK; h[1] += c; + c = h[1] >> 51; h[1] &= FE51_MASK; h[2] += c; + c = h[2] >> 51; h[2] &= FE51_MASK; h[3] += c; + c = h[3] >> 51; h[3] &= FE51_MASK; h[4] += c; + c = h[4] >> 51; h[4] &= FE51_MASK; h[0] += c * 19; + c = h[0] >> 51; h[0] &= FE51_MASK; h[1] += c; +} + +/* The canonical value, 0 <= h < p, little-endian. */ +static void fe51_tobytes(uint8_t *s, const fe51 f) +{ + fe51 h; + uint64_t q, w[4]; + unsigned i; + for (i = 0; i < 5; i++) + h[i] = f[i]; + fe51_carry(h); + fe51_carry(h); + /* h < 2^255 now; subtract p once if h >= p: q = 1 iff h + 19 >= 2^255 */ + q = (h[0] + 19) >> 51; + q = (h[1] + q) >> 51; + q = (h[2] + q) >> 51; + q = (h[3] + q) >> 51; + q = (h[4] + q) >> 51; + h[0] += 19 * q; + h[1] += h[0] >> 51; h[0] &= FE51_MASK; + h[2] += h[1] >> 51; h[1] &= FE51_MASK; + h[3] += h[2] >> 51; h[2] &= FE51_MASK; + h[4] += h[3] >> 51; h[3] &= FE51_MASK; + h[4] &= FE51_MASK; /* drops the 2^255 the +19 made */ + w[0] = h[0] | (h[1] << 51); + w[1] = (h[1] >> 13) | (h[2] << 38); + w[2] = (h[2] >> 26) | (h[3] << 25); + w[3] = (h[3] >> 39) | (h[4] << 12); + for (i = 0; i < 32; i++) + s[i] = (uint8_t)(w[i >> 3] >> (8 * (i & 7))); +} + +static void fe51_add(fe51 h, const fe51 f, const fe51 g) +{ + unsigned i; + for (i = 0; i < 5; i++) + h[i] = f[i] + g[i]; +} + +/* f + 2p - g: never negative for reduced limbs */ +static void fe51_sub(fe51 h, const fe51 f, const fe51 g) +{ + h[0] = f[0] + (uint64_t)0xfffffffffffdaULL - g[0]; + h[1] = f[1] + (uint64_t)0xffffffffffffeULL - g[1]; + h[2] = f[2] + (uint64_t)0xffffffffffffeULL - g[2]; + h[3] = f[3] + (uint64_t)0xffffffffffffeULL - g[3]; + h[4] = f[4] + (uint64_t)0xffffffffffffeULL - g[4]; +} + +static void fe51_mul(fe51 h, const fe51 f, const fe51 g) +{ + fe51_u128 r0, r1, r2, r3, r4; + uint64_t g1_19 = g[1] * 19, g2_19 = g[2] * 19, g3_19 = g[3] * 19, g4_19 = g[4] * 19; + uint64_t c; + r0 = (fe51_u128)f[0] * g[0] + (fe51_u128)f[1] * g4_19 + (fe51_u128)f[2] * g3_19 + + (fe51_u128)f[3] * g2_19 + (fe51_u128)f[4] * g1_19; + r1 = (fe51_u128)f[0] * g[1] + (fe51_u128)f[1] * g[0] + (fe51_u128)f[2] * g4_19 + + (fe51_u128)f[3] * g3_19 + (fe51_u128)f[4] * g2_19; + r2 = (fe51_u128)f[0] * g[2] + (fe51_u128)f[1] * g[1] + (fe51_u128)f[2] * g[0] + + (fe51_u128)f[3] * g4_19 + (fe51_u128)f[4] * g3_19; + r3 = (fe51_u128)f[0] * g[3] + (fe51_u128)f[1] * g[2] + (fe51_u128)f[2] * g[1] + + (fe51_u128)f[3] * g[0] + (fe51_u128)f[4] * g4_19; + r4 = (fe51_u128)f[0] * g[4] + (fe51_u128)f[1] * g[3] + (fe51_u128)f[2] * g[2] + + (fe51_u128)f[3] * g[1] + (fe51_u128)f[4] * g[0]; + c = (uint64_t)(r0 >> 51); h[0] = (uint64_t)r0 & FE51_MASK; r1 += c; + c = (uint64_t)(r1 >> 51); h[1] = (uint64_t)r1 & FE51_MASK; r2 += c; + c = (uint64_t)(r2 >> 51); h[2] = (uint64_t)r2 & FE51_MASK; r3 += c; + c = (uint64_t)(r3 >> 51); h[3] = (uint64_t)r3 & FE51_MASK; r4 += c; + c = (uint64_t)(r4 >> 51); h[4] = (uint64_t)r4 & FE51_MASK; + h[0] += c * 19; + c = h[0] >> 51; h[0] &= FE51_MASK; h[1] += c; +} + +static void fe51_sq(fe51 h, const fe51 f) +{ + fe51_mul(h, f, f); +} + +static void fe51_sqn(fe51 h, const fe51 f, unsigned n) +{ + fe51_sq(h, f); + while (--n) + fe51_sq(h, h); +} + +/* 121665 f (a24 = (486662 - 2) / 4) */ +static void fe51_mul_a24(fe51 h, const fe51 f) +{ + fe51_u128 r; + uint64_t c = 0; + unsigned i; + for (i = 0; i < 5; i++) + { + r = (fe51_u128)f[i] * 121665 + c; + h[i] = (uint64_t)r & FE51_MASK; + c = (uint64_t)(r >> 51); + } + h[0] += c * 19; + c = h[0] >> 51; h[0] &= FE51_MASK; h[1] += c; +} + +/* z^(p - 2) = z^(2^255 - 21): the inverse, by an addition chain of + * 254 squarings and 11 multiplies */ +static void fe51_invert(fe51 out, const fe51 z) +{ + fe51 z2, z9, z11, z_5, z_10, z_20, z_50, z_100, t; + fe51_sq(z2, z); /* 2 */ + fe51_sqn(t, z2, 2); /* 8 */ + fe51_mul(z9, t, z); /* 9 */ + fe51_mul(z11, z9, z2); /* 11 */ + fe51_sq(t, z11); /* 22 */ + fe51_mul(z_5, t, z9); /* 2^5 - 1 */ + fe51_sqn(t, z_5, 5); + fe51_mul(z_10, t, z_5); /* 2^10 - 1 */ + fe51_sqn(t, z_10, 10); + fe51_mul(z_20, t, z_10); /* 2^20 - 1 */ + fe51_sqn(t, z_20, 20); + fe51_mul(t, t, z_20); /* 2^40 - 1 */ + fe51_sqn(t, t, 10); + fe51_mul(z_50, t, z_10); /* 2^50 - 1 */ + fe51_sqn(t, z_50, 50); + fe51_mul(z_100, t, z_50); /* 2^100 - 1 */ + fe51_sqn(t, z_100, 100); + fe51_mul(t, t, z_100); /* 2^200 - 1 */ + fe51_sqn(t, t, 50); + fe51_mul(t, t, z_50); /* 2^250 - 1 */ + fe51_sqn(t, t, 5); /* 2^255 - 32 */ + fe51_mul(out, t, z11); /* 2^255 - 21 */ +} + +static void fe51_cswap(uint64_t swap, fe51 a, fe51 b) +{ + uint64_t mask = (uint64_t)0 - swap; + unsigned i; + for (i = 0; i < 5; i++) + { + uint64_t t = mask & (a[i] ^ b[i]); + a[i] ^= t; + b[i] ^= t; + } +} + +static int x25519_51(uint8_t *out, const uint8_t *scalar, const uint8_t *u_le) +{ + fe51 x1, x2, z2, x3, z3, A, AA, B, BB, E, C, D, DA, CB, t; + uint64_t swap = 0; + uint8_t k[32]; + uint8_t acc = 0; + int i; + + memcpy(k, scalar, 32); + k[0] &= 248; + k[31] &= 127; + k[31] |= 64; + fe51_frombytes(x1, u_le); + x2[0] = 1; x2[1] = x2[2] = x2[3] = x2[4] = 0; + z2[0] = z2[1] = z2[2] = z2[3] = z2[4] = 0; + for (i = 0; i < 5; i++) + x3[i] = x1[i]; + z3[0] = 1; z3[1] = z3[2] = z3[3] = z3[4] = 0; + + for (i = 254; i >= 0; i--) + { + uint64_t bit = (uint64_t)((k[i >> 3] >> (i & 7)) & 1); + swap ^= bit; + fe51_cswap(swap, x2, x3); + fe51_cswap(swap, z2, z3); + swap = bit; + + fe51_add(A, x2, z2); + fe51_sq(AA, A); + fe51_sub(B, x2, z2); + fe51_sq(BB, B); + fe51_sub(E, AA, BB); + fe51_add(C, x3, z3); + fe51_sub(D, x3, z3); + fe51_mul(DA, D, A); + fe51_mul(CB, C, B); + fe51_add(t, DA, CB); + fe51_sq(x3, t); + fe51_sub(t, DA, CB); + fe51_sq(t, t); + fe51_mul(z3, x1, t); + fe51_mul(x2, AA, BB); + fe51_mul_a24(t, E); + fe51_add(t, AA, t); + fe51_mul(z2, E, t); + } + fe51_cswap(swap, x2, x3); + fe51_cswap(swap, z2, z3); + + fe51_invert(t, z2); + fe51_mul(t, x2, t); + fe51_tobytes(out, t); + crypto_memzero(k, sizeof(k)); + /* all-zero output means a low-order point: refuse it */ + for (i = 0; i < 32; i++) + acc |= out[i]; + return acc ? 0 : -1; +} +#else +/* Machines without a 128-bit integer (32-bit ones, MSVC): arithmetic + * mod 2^255 - 19 in ten limbs of 26 and 25 bits, at bit offsets 0, 26, + * 51, 77, 102, 128, 153, 179, 204 and 230. Limbs are 32-bit words and + * every product one 32x32->64 multiply. Limb i times limb j lands in + * limb i + j; when both are odd the offsets add to one more than that + * limb's, a factor 2, and past limb 9 the product wraps to limb + * i + j - 10 times 19 (2^255 = 19 mod p). Inputs to a multiply stay + * under 2^27 a limb, so 19 g and 2 f fit 32 bits and a column of ten + * products is under 2^63. All unsigned: no shift of a negative. */ +typedef uint32_t fe10[10]; +static const unsigned char fe10_off[10] = { 0, 26, 51, 77, 102, 128, 153, 179, 204, 230 }; +static const unsigned char fe10_bits[10] = { 26, 25, 26, 25, 26, 25, 26, 25, 26, 25 }; +/* 2p in this radix: 2(2^26 - 19), then 2(2^25 - 1), 2(2^26 - 1), ... */ +static const uint32_t fe10_2p[10] = { + 0x7ffffdaU, 0x3fffffeU, 0x7fffffeU, 0x3fffffeU, 0x7fffffeU, + 0x3fffffeU, 0x7fffffeU, 0x3fffffeU, 0x7fffffeU, 0x3fffffeU +}; + +/* r (column sums under 2^63) carried into limbs h */ +static void fe10_carry64(fe10 h, uint64_t *r) +{ + uint64_t c; + unsigned i; + for (i = 0; i < 9; i++) + { + c = r[i] >> fe10_bits[i]; + h[i] = (uint32_t)r[i] & ((1U << fe10_bits[i]) - 1); + r[i + 1] += c; + } + c = r[9] >> 25; + h[9] = (uint32_t)r[9] & ((1U << 25) - 1); + c = h[0] + 19 * c; + h[0] = (uint32_t)c & ((1U << 26) - 1); + h[1] += (uint32_t)(c >> 26); +} + +static void fe10_carry(fe10 h) +{ + uint64_t r[10]; + unsigned i; + for (i = 0; i < 10; i++) + r[i] = h[i]; + fe10_carry64(h, r); +} + +static void fe10_frombytes(fe10 h, const uint8_t *s) +{ + uint64_t w[4]; + unsigned i; + for (i = 0; i < 4; i++) + w[i] = (uint64_t)s[8 * i] | ((uint64_t)s[8 * i + 1] << 8) + | ((uint64_t)s[8 * i + 2] << 16) | ((uint64_t)s[8 * i + 3] << 24) + | ((uint64_t)s[8 * i + 4] << 32) | ((uint64_t)s[8 * i + 5] << 40) + | ((uint64_t)s[8 * i + 6] << 48) | ((uint64_t)s[8 * i + 7] << 56); + for (i = 0; i < 10; i++) + { + /* the limb's bits from its offset, across two words at most */ + unsigned word = fe10_off[i] >> 6, sh = fe10_off[i] & 63; + uint64_t v = w[word] >> sh; + if (sh + fe10_bits[i] > 64) + v |= w[word + 1] << (64 - sh); + h[i] = (uint32_t)v & ((1U << fe10_bits[i]) - 1); + } + /* limb 9 is 25 bits from bit 230: bit 255 is left out */ +} + +/* The canonical value, 0 <= h < p, little-endian. */ +static void fe10_tobytes(uint8_t *s, const fe10 f) +{ + fe10 h; + uint64_t w[4]; + uint32_t q; + unsigned i; + for (i = 0; i < 10; i++) + h[i] = f[i]; + fe10_carry(h); + fe10_carry(h); + /* h < 2^255; q = 1 iff h + 19 >= 2^255, i.e. h >= p */ + q = (h[0] + 19) >> 26; + for (i = 1; i < 10; i++) + q = (h[i] + q) >> fe10_bits[i]; + h[0] += 19 * q; + for (i = 0; i < 9; i++) + { + h[i + 1] += h[i] >> fe10_bits[i]; + h[i] &= (1U << fe10_bits[i]) - 1; + } + h[9] &= (1U << 25) - 1; /* drops the 2^255 the +19 made */ + w[0] = w[1] = w[2] = w[3] = 0; + for (i = 0; i < 10; i++) + { + unsigned word = fe10_off[i] >> 6, sh = fe10_off[i] & 63; + w[word] |= (uint64_t)h[i] << sh; + if (sh + fe10_bits[i] > 64) + w[word + 1] |= (uint64_t)h[i] >> (64 - sh); + } + for (i = 0; i < 32; i++) + s[i] = (uint8_t)(w[i >> 3] >> (8 * (i & 7))); +} + +static void fe10_add(fe10 h, const fe10 f, const fe10 g) +{ + unsigned i; + for (i = 0; i < 10; i++) + h[i] = f[i] + g[i]; +} + +static void fe10_sub(fe10 h, const fe10 f, const fe10 g) +{ + unsigned i; + for (i = 0; i < 10; i++) + h[i] = f[i] + fe10_2p[i] - g[i]; + fe10_carry(h); +} + +static void fe10_mul(fe10 h, const fe10 f, const fe10 g) +{ + uint32_t f2[10], g19[10]; + uint64_t r[10]; + unsigned i; + for (i = 0; i < 10; i++) + { + f2[i] = 2 * f[i]; + g19[i] = 19 * g[i]; + } + r[0] = (uint64_t)f[0] * g[0] + + (uint64_t)f2[1] * g19[9] + + (uint64_t)f[2] * g19[8] + + (uint64_t)f2[3] * g19[7] + + (uint64_t)f[4] * g19[6] + + (uint64_t)f2[5] * g19[5] + + (uint64_t)f[6] * g19[4] + + (uint64_t)f2[7] * g19[3] + + (uint64_t)f[8] * g19[2] + + (uint64_t)f2[9] * g19[1]; + r[1] = (uint64_t)f[0] * g[1] + + (uint64_t)f[1] * g[0] + + (uint64_t)f[2] * g19[9] + + (uint64_t)f[3] * g19[8] + + (uint64_t)f[4] * g19[7] + + (uint64_t)f[5] * g19[6] + + (uint64_t)f[6] * g19[5] + + (uint64_t)f[7] * g19[4] + + (uint64_t)f[8] * g19[3] + + (uint64_t)f[9] * g19[2]; + r[2] = (uint64_t)f[0] * g[2] + + (uint64_t)f2[1] * g[1] + + (uint64_t)f[2] * g[0] + + (uint64_t)f2[3] * g19[9] + + (uint64_t)f[4] * g19[8] + + (uint64_t)f2[5] * g19[7] + + (uint64_t)f[6] * g19[6] + + (uint64_t)f2[7] * g19[5] + + (uint64_t)f[8] * g19[4] + + (uint64_t)f2[9] * g19[3]; + r[3] = (uint64_t)f[0] * g[3] + + (uint64_t)f[1] * g[2] + + (uint64_t)f[2] * g[1] + + (uint64_t)f[3] * g[0] + + (uint64_t)f[4] * g19[9] + + (uint64_t)f[5] * g19[8] + + (uint64_t)f[6] * g19[7] + + (uint64_t)f[7] * g19[6] + + (uint64_t)f[8] * g19[5] + + (uint64_t)f[9] * g19[4]; + r[4] = (uint64_t)f[0] * g[4] + + (uint64_t)f2[1] * g[3] + + (uint64_t)f[2] * g[2] + + (uint64_t)f2[3] * g[1] + + (uint64_t)f[4] * g[0] + + (uint64_t)f2[5] * g19[9] + + (uint64_t)f[6] * g19[8] + + (uint64_t)f2[7] * g19[7] + + (uint64_t)f[8] * g19[6] + + (uint64_t)f2[9] * g19[5]; + r[5] = (uint64_t)f[0] * g[5] + + (uint64_t)f[1] * g[4] + + (uint64_t)f[2] * g[3] + + (uint64_t)f[3] * g[2] + + (uint64_t)f[4] * g[1] + + (uint64_t)f[5] * g[0] + + (uint64_t)f[6] * g19[9] + + (uint64_t)f[7] * g19[8] + + (uint64_t)f[8] * g19[7] + + (uint64_t)f[9] * g19[6]; + r[6] = (uint64_t)f[0] * g[6] + + (uint64_t)f2[1] * g[5] + + (uint64_t)f[2] * g[4] + + (uint64_t)f2[3] * g[3] + + (uint64_t)f[4] * g[2] + + (uint64_t)f2[5] * g[1] + + (uint64_t)f[6] * g[0] + + (uint64_t)f2[7] * g19[9] + + (uint64_t)f[8] * g19[8] + + (uint64_t)f2[9] * g19[7]; + r[7] = (uint64_t)f[0] * g[7] + + (uint64_t)f[1] * g[6] + + (uint64_t)f[2] * g[5] + + (uint64_t)f[3] * g[4] + + (uint64_t)f[4] * g[3] + + (uint64_t)f[5] * g[2] + + (uint64_t)f[6] * g[1] + + (uint64_t)f[7] * g[0] + + (uint64_t)f[8] * g19[9] + + (uint64_t)f[9] * g19[8]; + r[8] = (uint64_t)f[0] * g[8] + + (uint64_t)f2[1] * g[7] + + (uint64_t)f[2] * g[6] + + (uint64_t)f2[3] * g[5] + + (uint64_t)f[4] * g[4] + + (uint64_t)f2[5] * g[3] + + (uint64_t)f[6] * g[2] + + (uint64_t)f2[7] * g[1] + + (uint64_t)f[8] * g[0] + + (uint64_t)f2[9] * g19[9]; + r[9] = (uint64_t)f[0] * g[9] + + (uint64_t)f[1] * g[8] + + (uint64_t)f[2] * g[7] + + (uint64_t)f[3] * g[6] + + (uint64_t)f[4] * g[5] + + (uint64_t)f[5] * g[4] + + (uint64_t)f[6] * g[3] + + (uint64_t)f[7] * g[2] + + (uint64_t)f[8] * g[1] + + (uint64_t)f[9] * g[0]; + fe10_carry64(h, r); +} + +static void fe10_sq(fe10 h, const fe10 f) +{ + fe10_mul(h, f, f); +} + +static void fe10_sqn(fe10 h, const fe10 f, unsigned n) +{ + fe10_sq(h, f); + while (--n) + fe10_sq(h, h); +} + +static void fe10_mul_a24(fe10 h, const fe10 f) +{ + uint64_t r[10]; + unsigned i; + for (i = 0; i < 10; i++) + r[i] = (uint64_t)f[i] * 121665; + fe10_carry64(h, r); +} + +/* z^(2^255 - 21), the same addition chain as the 51-bit form */ +static void fe10_invert(fe10 out, const fe10 z) +{ + fe10 z2, z9, z11, z_5, z_10, z_20, z_50, z_100, t; + fe10_sq(z2, z); + fe10_sqn(t, z2, 2); + fe10_mul(z9, t, z); + fe10_mul(z11, z9, z2); + fe10_sq(t, z11); + fe10_mul(z_5, t, z9); + fe10_sqn(t, z_5, 5); + fe10_mul(z_10, t, z_5); + fe10_sqn(t, z_10, 10); + fe10_mul(z_20, t, z_10); + fe10_sqn(t, z_20, 20); + fe10_mul(t, t, z_20); + fe10_sqn(t, t, 10); + fe10_mul(z_50, t, z_10); + fe10_sqn(t, z_50, 50); + fe10_mul(z_100, t, z_50); + fe10_sqn(t, z_100, 100); + fe10_mul(t, t, z_100); + fe10_sqn(t, t, 50); + fe10_mul(t, t, z_50); + fe10_sqn(t, t, 5); + fe10_mul(out, t, z11); +} + +static void fe10_cswap(uint32_t swap, fe10 a, fe10 b) +{ + uint32_t mask = (uint32_t)0 - swap; + unsigned i; + for (i = 0; i < 10; i++) + { + uint32_t t = mask & (a[i] ^ b[i]); + a[i] ^= t; + b[i] ^= t; + } +} + +static int x25519_10(uint8_t *out, const uint8_t *scalar, const uint8_t *u_le) +{ + fe10 x1, x2, z2, x3, z3, A, AA, B, BB, E, C, D, DA, CB, t; + uint32_t swap = 0; + uint8_t k[32]; + uint8_t acc = 0; + int i; + + memcpy(k, scalar, 32); + k[0] &= 248; + k[31] &= 127; + k[31] |= 64; + fe10_frombytes(x1, u_le); + for (i = 0; i < 10; i++) + { + x2[i] = z2[i] = z3[i] = 0; + x3[i] = x1[i]; + } + x2[0] = 1; + z3[0] = 1; + + for (i = 254; i >= 0; i--) + { + uint32_t bit = (uint32_t)((k[i >> 3] >> (i & 7)) & 1); + swap ^= bit; + fe10_cswap(swap, x2, x3); + fe10_cswap(swap, z2, z3); + swap = bit; + + fe10_add(A, x2, z2); + fe10_sq(AA, A); + fe10_sub(B, x2, z2); + fe10_sq(BB, B); + fe10_sub(E, AA, BB); + fe10_add(C, x3, z3); + fe10_sub(D, x3, z3); + fe10_mul(DA, D, A); + fe10_mul(CB, C, B); + fe10_add(t, DA, CB); + fe10_sq(x3, t); + fe10_sub(t, DA, CB); + fe10_sq(t, t); + fe10_mul(z3, x1, t); + fe10_mul(x2, AA, BB); + fe10_mul_a24(t, E); + fe10_add(t, AA, t); + fe10_mul(z2, E, t); + } + fe10_cswap(swap, x2, x3); + fe10_cswap(swap, z2, z3); + + fe10_invert(t, z2); + fe10_mul(t, x2, t); + fe10_tobytes(out, t); + crypto_memzero(k, sizeof(k)); + for (i = 0; i < 32; i++) + acc |= out[i]; + return acc ? 0 : -1; +} +#endif + +int x25519(uint8_t *out, const uint8_t *scalar, const uint8_t *u_le) +{ +#if defined(__SIZEOF_INT128__) && !defined(X25519_NO_128) + return x25519_51(out, scalar, u_le); +#else + return x25519_10(out, scalar, u_le); +#endif +} + +int x25519_base(uint8_t *out, const uint8_t *scalar) +{ + uint8_t nine[32] = {9}; + return x25519(out, scalar, nine); +} + +/* Largest curve here is P-384. */ +#define EC_MAX_K (384 / BN_WORD_BITS) + +static const uint8_t p256_p_be[32] = { + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff +}; +static const uint8_t p256_n_be[32] = { + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xbc,0xe6,0xfa,0xad,0xa7,0x17,0x9e,0x84,0xf3,0xb9,0xca,0xc2,0xfc,0x63,0x25,0x51 +}; +static const uint8_t p256_b_be[32] = { + 0x5a,0xc6,0x35,0xd8,0xaa,0x3a,0x93,0xe7,0xb3,0xeb,0xbd,0x55,0x76,0x98,0x86,0xbc, + 0x65,0x1d,0x06,0xb0,0xcc,0x53,0xb0,0xf6,0x3b,0xce,0x3c,0x3e,0x27,0xd2,0x60,0x4b +}; +static const uint8_t p256_gx_be[32] = { + 0x6b,0x17,0xd1,0xf2,0xe1,0x2c,0x42,0x47,0xf8,0xbc,0xe6,0xe5,0x63,0xa4,0x40,0xf2, + 0x77,0x03,0x7d,0x81,0x2d,0xeb,0x33,0xa0,0xf4,0xa1,0x39,0x45,0xd8,0x98,0xc2,0x96 +}; +static const uint8_t p256_gy_be[32] = { + 0x4f,0xe3,0x42,0xe2,0xfe,0x1a,0x7f,0x9b,0x8e,0xe7,0xeb,0x4a,0x7c,0x0f,0x9e,0x16, + 0x2b,0xce,0x33,0x57,0x6b,0x31,0x5e,0xce,0xcb,0xb6,0x40,0x68,0x37,0xbf,0x51,0xf5 +}; + +static const uint8_t p384_p_be[48] = { + 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,0xfe, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff +}; +static const uint8_t p384_n_be[48] = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xc7,0x63,0x4d,0x81,0xf4,0x37,0x2d,0xdf, + 0x58,0x1a,0x0d,0xb2,0x48,0xb0,0xa7,0x7a,0xec,0xec,0x19,0x6a,0xcc,0xc5,0x29,0x73 +}; +static const uint8_t p384_b_be[48] = { + 0xb3,0x31,0x2f,0xa7,0xe2,0x3e,0xe7,0xe4,0x98,0x8e,0x05,0x6b,0xe3,0xf8,0x2d,0x19, + 0x18,0x1d,0x9c,0x6e,0xfe,0x81,0x41,0x12,0x03,0x14,0x08,0x8f,0x50,0x13,0x87,0x5a, + 0xc6,0x56,0x39,0x8d,0x8a,0x2e,0xd1,0x9d,0x2a,0x85,0xc8,0xed,0xd3,0xec,0x2a,0xef +}; +static const uint8_t p384_gx_be[48] = { + 0xaa,0x87,0xca,0x22,0xbe,0x8b,0x05,0x37,0x8e,0xb1,0xc7,0x1e,0xf3,0x20,0xad,0x74, + 0x6e,0x1d,0x3b,0x62,0x8b,0xa7,0x9b,0x98,0x59,0xf7,0x41,0xe0,0x82,0x54,0x2a,0x38, + 0x55,0x02,0xf2,0x5d,0xbf,0x55,0x29,0x6c,0x3a,0x54,0x5e,0x38,0x72,0x76,0x0a,0xb7 +}; +static const uint8_t p384_gy_be[48] = { + 0x36,0x17,0xde,0x4a,0x96,0x26,0x2c,0x6f,0x5d,0x9e,0x98,0xbf,0x92,0x92,0xdc,0x29, + 0xf8,0xf4,0x1d,0xbd,0x28,0x9a,0x14,0x7c,0xe9,0xda,0x31,0x13,0xb5,0xf0,0xb8,0xc0, + 0x0a,0x60,0xb1,0xce,0x1d,0x7e,0x81,0x9d,0x7a,0x43,0x1d,0x7c,0x90,0xea,0x0e,0x5f +}; + +/* Jacobian point, coordinates in the Montgomery domain of p. + * Infinity is z == 0. */ +struct ec_pt +{ + bn_word x[EC_MAX_K]; + bn_word y[EC_MAX_K]; + bn_word z[EC_MAX_K]; +}; + +/* Curve constants, derived once from the octet strings above. */ +struct ec_curve +{ + bn_word p[EC_MAX_K]; + bn_word n[EC_MAX_K]; + bn_word r2p[EC_MAX_K]; + bn_word r2n[EC_MAX_K]; + bn_word one[EC_MAX_K]; /* R mod p */ + bn_word b[EC_MAX_K]; /* b in Montgomery form */ + bn_word pm2[EC_MAX_K]; /* p - 2, inversion exponent */ + bn_word nm2[EC_MAX_K]; /* n - 2 */ + struct ec_pt g; + bn_word n0p; + bn_word n0n; + const uint8_t *p_be, *n_be, *b_be, *gx_be, *gy_be; + unsigned k; /* words per element */ + unsigned bits; /* 256 or 384 */ + unsigned bytes; /* 32 or 48 */ + retro_atomic_int_t ready; +}; + +static struct ec_curve ec_p256 = { {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + { {0}, {0}, {0} }, 0, 0, + p256_p_be, p256_n_be, p256_b_be, p256_gx_be, p256_gy_be, + 256 / BN_WORD_BITS, 256, 32, 0 }; +static struct ec_curve ec_p384 = { {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + { {0}, {0}, {0} }, 0, 0, + p384_p_be, p384_n_be, p384_b_be, p384_gx_be, p384_gy_be, + 384 / BN_WORD_BITS, 384, 48, 0 }; + +/* The curve constants are computed on first use. Two threads making + * their first connections at once both get here; the one that moves + * ready from 0 to EC_BUSY computes them into the curve and stores + * EC_READY with release once every field is in place. The other does + * not wait for it: it computes a copy of its own for this call. */ +#define EC_BUSY 1 +#define EC_READY 2 +#if defined(HAVE_THREADS) && defined(EC_TEST_WAIT) +#include +#endif + +static void ec_compute(struct ec_curve *cv) +{ + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + bn_word two[EC_MAX_K]; + const unsigned k = cv->k; + +#ifdef EC_TEST_HOLD + EC_TEST_HOLD(); +#endif + + bn_from_be(cv->p, k, cv->p_be, cv->bytes); + bn_from_be(cv->n, k, cv->n_be, cv->bytes); + cv->n0p = bn_mont_n0(cv->p); + cv->n0n = bn_mont_n0(cv->n); + bn_mont_r2(cv->r2p, cv->p, k); + bn_mont_r2(cv->r2n, cv->n, k); + + bn_zero(two, k); + two[0] = 1; + bn_mont_mul(cv->one, cv->r2p, two, cv->p, cv->n0p, k, tmp); + + two[0] = 2; + bn_sub(cv->pm2, cv->p, two, k); + bn_sub(cv->nm2, cv->n, two, k); + + bn_from_be(cv->b, k, cv->b_be, cv->bytes); + bn_mont_mul(cv->b, cv->b, cv->r2p, cv->p, cv->n0p, k, tmp); + + bn_from_be(cv->g.x, k, cv->gx_be, cv->bytes); + bn_from_be(cv->g.y, k, cv->gy_be, cv->bytes); + bn_mont_mul(cv->g.x, cv->g.x, cv->r2p, cv->p, cv->n0p, k, tmp); + bn_mont_mul(cv->g.y, cv->g.y, cv->r2p, cv->p, cv->n0p, k, tmp); + bn_copy(cv->g.z, cv->one, k); +} + +/* The curve's constants, computed into @cv on first use, or into @own + * while another thread is computing @cv's. */ +static const struct ec_curve *ec_get(struct ec_curve *cv, + struct ec_curve *own) +{ +#ifdef HAVE_THREADS + int ready = retro_atomic_load_acquire_int(&cv->ready); + if (ready == EC_READY) + return cv; + if (!ready && retro_atomic_cas_int(&cv->ready, 0, EC_BUSY)) + { + ec_compute(cv); + retro_atomic_store_release_int(&cv->ready, EC_READY); + return cv; + } +#ifdef EC_TEST_WAIT + while (retro_atomic_load_acquire_int(&cv->ready) != EC_READY) + sthread_yield(); + return cv; +#endif + own->p_be = cv->p_be; + own->n_be = cv->n_be; + own->b_be = cv->b_be; + own->gx_be = cv->gx_be; + own->gy_be = cv->gy_be; + own->k = cv->k; + own->bits = cv->bits; + own->bytes = cv->bytes; + ec_compute(own); + return own; +#else + (void)own; + if (retro_atomic_load_acquire_int(&cv->ready) != EC_READY) + { + ec_compute(cv); + retro_atomic_store_release_int(&cv->ready, EC_READY); + } + return cv; +#endif +} + +/* Field helpers, all in the Montgomery domain. */ +/* The word count is a run-time field, which stops the compiler from + * unrolling the limb loops; P-256 is the curve on the TLS fast path, + * so its length is spelled as a constant on that branch and the + * compiler clones the Montgomery routines for it. */ +#define EC_P256_K (256 / BN_WORD_BITS) + +static void p256_fmul(const struct ec_curve *cv, bn_word *r, + const bn_word *a, const bn_word *b) +{ + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + if (cv->k == EC_P256_K) + bn_mont_mul(r, a, b, cv->p, cv->n0p, EC_P256_K, tmp); + else + bn_mont_mul(r, a, b, cv->p, cv->n0p, cv->k, tmp); +} + +static void p256_fsqr(const struct ec_curve *cv, bn_word *r, const bn_word *a) +{ + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + if (cv->k == EC_P256_K) + bn_mont_sqr(r, a, cv->p, cv->n0p, EC_P256_K, tmp); + else + bn_mont_sqr(r, a, cv->p, cv->n0p, cv->k, tmp); +} + +static void p256_fadd(const struct ec_curve *cv, bn_word *r, + const bn_word *a, const bn_word *b) +{ + if (cv->k == EC_P256_K) + bn_mod_add(r, a, b, cv->p, EC_P256_K); + else + bn_mod_add(r, a, b, cv->p, cv->k); +} + +static void p256_fsub(const struct ec_curve *cv, bn_word *r, + const bn_word *a, const bn_word *b) +{ + if (cv->k == EC_P256_K) + bn_mod_sub(r, a, b, cv->p, EC_P256_K); + else + bn_mod_sub(r, a, b, cv->p, cv->k); +} + +/* r = a^e in the Montgomery domain (a is aR, so is the result). */ +static void p256_fexp(const struct ec_curve *cv, bn_word *r, + const bn_word *a, const bn_word *e, + const bn_word *m, bn_word n0, const bn_word *one) +{ + bn_word acc[EC_MAX_K]; + bn_word t[EC_MAX_K]; + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + unsigned i; + + bn_copy(acc, one, cv->k); + for (i = cv->bits; i-- > 0; ) + { + bn_mont_sqr(t, acc, m, n0, cv->k, tmp); + if (bn_get_bit(e, i)) + bn_mont_mul(acc, t, a, m, n0, cv->k, tmp); + else + bn_copy(acc, t, cv->k); + } + bn_copy(r, acc, cv->k); +} + +static void p256_pt_double(const struct ec_curve *cv, struct ec_pt *r, const struct ec_pt *a) +{ + /* dbl-2001-b, a = -3 */ + bn_word delta[EC_MAX_K], gamma[EC_MAX_K], beta[EC_MAX_K], alpha[EC_MAX_K], t1[EC_MAX_K], t2[EC_MAX_K]; + + p256_fsqr(cv, delta, a->z); + p256_fsqr(cv, gamma, a->y); + p256_fmul(cv, beta, a->x, gamma); + p256_fsub(cv, t1, a->x, delta); + p256_fadd(cv, t2, a->x, delta); + p256_fmul(cv, alpha, t1, t2); + p256_fadd(cv, t1, alpha, alpha); + p256_fadd(cv, alpha, t1, alpha); /* 3 (x - d)(x + d) */ + + p256_fsqr(cv, t1, alpha); + p256_fadd(cv, t2, beta, beta); + p256_fadd(cv, t2, t2, t2); + p256_fadd(cv, t2, t2, t2); /* 8 beta */ + p256_fsub(cv, t1, t1, t2); /* x3 = alpha^2 - 8 beta */ + + p256_fadd(cv, t2, a->y, a->z); + p256_fsqr(cv, t2, t2); + p256_fsub(cv, t2, t2, gamma); + p256_fsub(cv, r->z, t2, delta); /* z3 = (y + z)^2 - gamma - delta */ + + p256_fadd(cv, t2, beta, beta); + p256_fadd(cv, t2, t2, t2); /* 4 beta */ + p256_fsub(cv, t2, t2, t1); /* 4 beta - x3 */ + p256_fmul(cv, t2, alpha, t2); + p256_fsqr(cv, gamma, gamma); + p256_fadd(cv, gamma, gamma, gamma); + p256_fadd(cv, gamma, gamma, gamma); + p256_fadd(cv, gamma, gamma, gamma); /* 8 gamma^2 */ + p256_fsub(cv, r->y, t2, gamma); + bn_copy(r->x, t1, cv->k); +} + +static void p256_pt_add(const struct ec_curve *cv, struct ec_pt *r, const struct ec_pt *a, + const struct ec_pt *b) +{ + /* add-2007-bl */ + bn_word z1z1[EC_MAX_K], z2z2[EC_MAX_K], u1[EC_MAX_K], u2[EC_MAX_K], s1[EC_MAX_K], s2[EC_MAX_K], h[EC_MAX_K], rr[EC_MAX_K]; + bn_word t[EC_MAX_K]; + + if (bn_is_zero(a->z, cv->k)) + { + *r = *b; + return; + } + if (bn_is_zero(b->z, cv->k)) + { + *r = *a; + return; + } + + p256_fsqr(cv, z1z1, a->z); + p256_fsqr(cv, z2z2, b->z); + p256_fmul(cv, u1, a->x, z2z2); + p256_fmul(cv, u2, b->x, z1z1); + p256_fmul(cv, s1, a->y, b->z); + p256_fmul(cv, s1, s1, z2z2); + p256_fmul(cv, s2, b->y, a->z); + p256_fmul(cv, s2, s2, z1z1); + p256_fsub(cv, h, u2, u1); + p256_fsub(cv, rr, s2, s1); + + if (bn_is_zero(h, cv->k)) + { + if (bn_is_zero(rr, cv->k)) + p256_pt_double(cv, r, a); + else + { + bn_zero(r->x, cv->k); + bn_zero(r->y, cv->k); + bn_zero(r->z, cv->k); + } + return; + } + + p256_fadd(cv, rr, rr, rr); /* r = 2 (s2 - s1) */ + p256_fadd(cv, t, h, h); + p256_fsqr(cv, t, t); /* i = (2h)^2 */ + p256_fmul(cv, u2, h, t); /* j = h i */ + p256_fmul(cv, u1, u1, t); /* v = u1 i */ + + p256_fsqr(cv, t, rr); + p256_fsub(cv, t, t, u2); + p256_fsub(cv, t, t, u1); + p256_fsub(cv, t, t, u1); /* x3 = r^2 - j - 2v */ + + p256_fsub(cv, u1, u1, t); /* v - x3 */ + p256_fmul(cv, u1, rr, u1); + p256_fmul(cv, s1, s1, u2); + p256_fadd(cv, s1, s1, s1); /* 2 s1 j */ + p256_fsub(cv, r->y, u1, s1); + + p256_fadd(cv, s2, a->z, b->z); + p256_fsqr(cv, s2, s2); + p256_fsub(cv, s2, s2, z1z1); + p256_fsub(cv, s2, s2, z2z2); + p256_fmul(cv, r->z, s2, h); + bn_copy(r->x, t, cv->k); +} + +static void p256_pt_select(const struct ec_curve *cv, struct ec_pt *r, bn_word bit, + const struct ec_pt *a, const struct ec_pt *b) +{ + bn_select(r->x, bit, a->x, b->x, cv->k); + bn_select(r->y, bit, a->y, b->y, cv->k); + bn_select(r->z, bit, a->z, b->z, cv->k); +} + +/* r = k * a. Double-and-always-add with a select, so the sequence of + * field operations does not depend on the scalar; what still does is + * the infinity test inside p256_pt_add for the leading zero bits. The + * fallback for p256_pt_mul() when its table cannot be had. */ +static void p256_pt_mul_bits(const struct ec_curve *cv, struct ec_pt *r, const bn_word *k, + const struct ec_pt *a) +{ + struct ec_pt acc, t; + unsigned i; + + bn_zero(acc.x, cv->k); + bn_zero(acc.y, cv->k); + bn_zero(acc.z, cv->k); + + for (i = cv->bits; i-- > 0; ) + { + p256_pt_double(cv, &acc, &acc); + p256_pt_add(cv, &t, &acc, a); + p256_pt_select(cv, &acc, bn_get_bit(k, i), &t, &acc); + } + *r = acc; +} + +/* Four scalar bits from bit 4w up. */ +static unsigned ec_digit4(const bn_word *k, unsigned w) +{ + return (unsigned)(bn_get_bit(k, 4 * w) + | (bn_get_bit(k, 4 * w + 1) << 1) + | (bn_get_bit(k, 4 * w + 2) << 2) + | (bn_get_bit(k, 4 * w + 3) << 3)); +} + +/* 1..15 times a in t[1..15]; t[0] unused. */ +static void ec_table4(const struct ec_curve *cv, struct ec_pt *t, const struct ec_pt *a) +{ + unsigned j; + t[1] = *a; + p256_pt_double(cv, &t[2], a); + for (j = 3; j < 16; j++) + p256_pt_add(cv, &t[j], &t[j - 1], a); +} + +/* r = k * a, four bits at a time: per window four doublings and one + * addition, where the bit-by-bit form does one addition per bit. The + * table entry is chosen by scanning all fifteen with a select, and a + * zero digit adds t[1] anyway and throws the sum away, so neither the + * memory touched nor the field operations follow the scalar, save the + * infinity test for its leading zero digits, as before. The table + * (fifteen points, 2 KiB for P-384) is on the heap: the stack budget. */ +static void p256_pt_mul(const struct ec_curve *cv, struct ec_pt *r, const bn_word *k, + const struct ec_pt *a) +{ + struct ec_pt *t = (struct ec_pt*)malloc(16 * sizeof(*t)); + struct ec_pt acc, sel, sum; + unsigned w, j; + + if (!t) + { + p256_pt_mul_bits(cv, r, k, a); + return; + } + ec_table4(cv, t, a); + bn_zero(acc.x, cv->k); + bn_zero(acc.y, cv->k); + bn_zero(acc.z, cv->k); + for (w = cv->bits / 4; w-- > 0; ) + { + unsigned d = ec_digit4(k, w); + p256_pt_double(cv, &acc, &acc); + p256_pt_double(cv, &acc, &acc); + p256_pt_double(cv, &acc, &acc); + p256_pt_double(cv, &acc, &acc); + sel = t[1]; + for (j = 2; j < 16; j++) + { + bn_word x = (bn_word)(j ^ d); + bn_word eq = (bn_word)1 ^ ((x | ((bn_word)0 - x)) >> (BN_WORD_BITS - 1)); + p256_pt_select(cv, &sel, eq, &t[j], &sel); + } + p256_pt_add(cv, &sum, &acc, &sel); + { + bn_word nz = ((bn_word)d | ((bn_word)0 - (bn_word)d)) >> (BN_WORD_BITS - 1); + p256_pt_select(cv, &acc, nz, &sum, &acc); + } + } + *r = acc; + crypto_memzero(t, 16 * sizeof(*t)); + crypto_memzero(&sel, sizeof(sel)); + crypto_memzero(&sum, sizeof(sum)); + free(t); +} + +/* r = u1 g + u2 q in one pass (Shamir's trick), four bits of each + * scalar per window: the doublings are shared. For signature checks, + * where every input is public, so the additions may skip zero digits. */ +static void p256_pt_mul2(const struct ec_curve *cv, struct ec_pt *r, + const bn_word *u1, const struct ec_pt *g, const bn_word *u2, const struct ec_pt *q) +{ + struct ec_pt *tg = (struct ec_pt*)malloc(32 * sizeof(*tg)); + struct ec_pt *tq; + struct ec_pt acc; + unsigned w; + + if (!tg) + { + struct ec_pt b; + p256_pt_mul_bits(cv, r, u1, g); + p256_pt_mul_bits(cv, &b, u2, q); + p256_pt_add(cv, r, r, &b); + return; + } + tq = tg + 16; + ec_table4(cv, tg, g); + ec_table4(cv, tq, q); + bn_zero(acc.x, cv->k); + bn_zero(acc.y, cv->k); + bn_zero(acc.z, cv->k); + for (w = cv->bits / 4; w-- > 0; ) + { + unsigned d1 = ec_digit4(u1, w), d2 = ec_digit4(u2, w); + p256_pt_double(cv, &acc, &acc); + p256_pt_double(cv, &acc, &acc); + p256_pt_double(cv, &acc, &acc); + p256_pt_double(cv, &acc, &acc); + if (d1) + p256_pt_add(cv, &acc, &acc, &tg[d1]); + if (d2) + p256_pt_add(cv, &acc, &acc, &tq[d2]); + } + *r = acc; + free(tg); +} + +/* Affine x, y (plain domain) from Jacobian. Returns -1 at infinity. */ +static int p256_pt_affine(const struct ec_curve *cv, bn_word *x, bn_word *y, const struct ec_pt *a) +{ + bn_word zi[EC_MAX_K], zi2[EC_MAX_K], one[EC_MAX_K]; + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + + if (bn_is_zero(a->z, cv->k)) + return -1; + + p256_fexp(cv, zi, a->z, cv->pm2, cv->p, cv->n0p, cv->one); + p256_fsqr(cv, zi2, zi); + p256_fmul(cv, x, a->x, zi2); + if (y) + { + p256_fmul(cv, zi, zi, zi2); + p256_fmul(cv, y, a->y, zi); + } + + /* Out of the Montgomery domain. */ + bn_zero(one, cv->k); + one[0] = 1; + bn_mont_mul(x, x, one, cv->p, cv->n0p, cv->k, tmp); + if (y) + bn_mont_mul(y, y, one, cv->p, cv->n0p, cv->k, tmp); + return 0; +} + +/* Parse 0x04 || X || Y, check both coordinates < p and the curve + * equation y^2 = x^3 - 3x + b. */ +static int p256_pt_load(const struct ec_curve *cv, struct ec_pt *r, const uint8_t *pub) +{ + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + bn_word lhs[EC_MAX_K], rhs[EC_MAX_K], t[EC_MAX_K]; + + if (pub[0] != 0x04) + return -1; + + bn_from_be(r->x, cv->k, pub + 1, cv->bytes); + bn_from_be(r->y, cv->k, pub + 1 + cv->bytes, cv->bytes); + if (bn_cmp(r->x, cv->p, cv->k) >= 0 || bn_cmp(r->y, cv->p, cv->k) >= 0) + return -1; + + bn_mont_mul(r->x, r->x, cv->r2p, cv->p, cv->n0p, cv->k, tmp); + bn_mont_mul(r->y, r->y, cv->r2p, cv->p, cv->n0p, cv->k, tmp); + bn_copy(r->z, cv->one, cv->k); + + p256_fsqr(cv, lhs, r->y); + p256_fsqr(cv, rhs, r->x); + p256_fmul(cv, rhs, rhs, r->x); + p256_fadd(cv, t, r->x, r->x); + p256_fadd(cv, t, t, r->x); + p256_fsub(cv, rhs, rhs, t); + p256_fadd(cv, rhs, rhs, cv->b); + return bn_cmp(lhs, rhs, cv->k) == 0 ? 0 : -1; +} + +static int p256_scalar_load(const struct ec_curve *cv, bn_word *k, const uint8_t *s) +{ + bn_from_be(k, cv->k, s, cv->bytes); + if (bn_is_zero(k, cv->k) || bn_cmp(k, cv->n, cv->k) >= 0) + return -1; + return 0; +} + +static int ec_keygen(const struct ec_curve *cv, const uint8_t *priv, uint8_t *pub) +{ + bn_word k[EC_MAX_K], x[EC_MAX_K], y[EC_MAX_K]; + struct ec_pt q; + int ret; + + if (p256_scalar_load(cv, k, priv) != 0) + return -1; + p256_pt_mul(cv, &q, k, &cv->g); + ret = p256_pt_affine(cv, x, y, &q); + crypto_memzero(k, sizeof(k)); + crypto_memzero(&q, sizeof(q)); + if (ret != 0) + return -1; + pub[0] = 0x04; + bn_to_be(pub + 1, cv->bytes, x, cv->k); + bn_to_be(pub + 1 + cv->bytes, cv->bytes, y, cv->k); + return 0; +} + +static int ec_ecdh(const struct ec_curve *cv, const uint8_t *priv, const uint8_t *peer, uint8_t *shared) +{ + bn_word k[EC_MAX_K], x[EC_MAX_K]; + struct ec_pt q; + int ret; + + if (p256_scalar_load(cv, k, priv) != 0 || p256_pt_load(cv, &q, peer) != 0) + return -1; + p256_pt_mul(cv, &q, k, &q); + ret = p256_pt_affine(cv, x, NULL, &q); + crypto_memzero(k, sizeof(k)); + crypto_memzero(&q, sizeof(q)); + if (ret != 0) + return -1; + bn_to_be(shared, cv->bytes, x, cv->k); + crypto_memzero(x, sizeof(x)); + return 0; +} + +static int ec_ecdsa_verify(const struct ec_curve *cv, const uint8_t *pub, + const uint8_t *digest, size_t digest_len, + const uint8_t *r, const uint8_t *s) +{ + bn_word rr[EC_MAX_K], ss[EC_MAX_K], e[EC_MAX_K], w[EC_MAX_K], u1[EC_MAX_K], u2[EC_MAX_K], x[EC_MAX_K]; + bn_word tmp[BN_MONT_TMP_WORDS(EC_MAX_K)]; + bn_word one[EC_MAX_K]; + struct ec_pt q, a; + + if (p256_pt_load(cv, &q, pub) != 0) + return -1; + if (p256_scalar_load(cv, rr, r) != 0 || p256_scalar_load(cv, ss, s) != 0) + return -1; + + /* e = leftmost 256 bits of the digest, reduced once. */ + bn_from_be(e, cv->k, digest, digest_len > cv->bytes ? cv->bytes : digest_len); + if (bn_cmp(e, cv->n, cv->k) >= 0) + bn_sub(e, e, cv->n, cv->k); + + /* w = s^-1 mod n: s^(n-2) in the Montgomery domain of n, then + * back out, so the multiplies below run in the plain domain. */ + bn_mont_mul(w, ss, cv->r2n, cv->n, cv->n0n, cv->k, tmp); + bn_zero(one, cv->k); + one[0] = 1; + bn_mont_mul(x, cv->r2n, one, cv->n, cv->n0n, cv->k, tmp); /* R mod n */ + p256_fexp(cv, w, w, cv->nm2, cv->n, cv->n0n, x); + bn_mont_mul(w, w, one, cv->n, cv->n0n, cv->k, tmp); + + bn_mod_mul(u1, e, w, cv->n, cv->r2n, cv->n0n, cv->k, tmp); + bn_mod_mul(u2, rr, w, cv->n, cv->r2n, cv->n0n, cv->k, tmp); + + p256_pt_mul2(cv, &a, u1, &cv->g, u2, &q); + if (p256_pt_affine(cv, x, NULL, &a) != 0) + return -1; + if (bn_cmp(x, cv->n, cv->k) >= 0) + bn_sub(x, x, cv->n, cv->k); + return bn_cmp(x, rr, cv->k) == 0 ? 0 : -1; +} + + +int p256_keygen(const uint8_t *priv, uint8_t *pub) +{ + struct ec_curve own; + return ec_keygen(ec_get(&ec_p256, &own), priv, pub); +} + +int p256_ecdh(const uint8_t *priv, const uint8_t *peer, uint8_t *shared) +{ + struct ec_curve own; + return ec_ecdh(ec_get(&ec_p256, &own), priv, peer, shared); +} + +int p256_ecdsa_verify(const uint8_t *pub, + const uint8_t *digest, size_t digest_len, + const uint8_t *r, const uint8_t *s) +{ + struct ec_curve own; + return ec_ecdsa_verify(ec_get(&ec_p256, &own), pub, digest, digest_len, r, s); +} + +int p384_keygen(const uint8_t *priv, uint8_t *pub) +{ + struct ec_curve own; + return ec_keygen(ec_get(&ec_p384, &own), priv, pub); +} + +int p384_ecdh(const uint8_t *priv, const uint8_t *peer, uint8_t *shared) +{ + struct ec_curve own; + return ec_ecdh(ec_get(&ec_p384, &own), priv, peer, shared); +} + +int p384_ecdsa_verify(const uint8_t *pub, + const uint8_t *digest, size_t digest_len, + const uint8_t *r, const uint8_t *s) +{ + struct ec_curve own; + return ec_ecdsa_verify(ec_get(&ec_p384, &own), pub, digest, digest_len, r, s); +} + +/* r = base^exp in the Montgomery domain of m, @base_m and @one_m (R mod m) + * there too. All @ebits bits are walked, four at a time, and the table + * entry is chosen by scanning all sixteen, so neither the operations nor + * the memory touched follow the exponent. Squarings go through + * bn_mont_mul(): its loops do not depend on the operands. @tab has + * 16 * k words, @tmp BN_MONT_TMP_WORDS(k). */ +static void bn_mont_exp_ct(bn_word *r, const bn_word *base_m, + const bn_word *exp, unsigned ebits, const bn_word *one_m, + const bn_word *m, bn_word n0, unsigned k, bn_word *tab, bn_word *tmp) +{ + bn_word sel[BN_MAX_WORDS]; + unsigned w, j; + + bn_copy(tab, one_m, k); + bn_copy(tab + k, base_m, k); + for (j = 2; j < 16; j++) + bn_mont_mul(tab + j * k, tab + (j - 1) * k, base_m, m, n0, k, tmp); + + bn_copy(r, one_m, k); + for (w = (ebits + 3) / 4; w-- > 0; ) + { + unsigned d = 0; + for (j = 0; j < 4; j++) + if (4 * w + j < ebits) + d |= bn_get_bit(exp, 4 * w + j) << j; + for (j = 0; j < 4; j++) + bn_mont_mul(r, r, r, m, n0, k, tmp); + bn_copy(sel, tab, k); + for (j = 1; j < 16; j++) + { + bn_word x = (bn_word)(j ^ d); + bn_word eq = (bn_word)1 ^ ((x | ((bn_word)0 - x)) >> (BN_WORD_BITS - 1)); + bn_select(sel, eq, tab + j * k, sel, k); + } + bn_mont_mul(r, r, sel, m, n0, k, tmp); + } + crypto_memzero(sel, sizeof(sel)); +} + +/* ---- RSA signing ---------------------------------------------------- */ + +/* Halves of a modulus up to BN_MAX_WORDS. */ +#define RSA_HALF_WORDS (BN_MAX_WORDS / 2) + +struct rsa_sign_work +{ + bn_word c[2 * RSA_HALF_WORDS]; + bn_word s[2 * RSA_HALF_WORDS]; + bn_word t[2 * RSA_HALF_WORDS]; + bn_word prime[2][RSA_HALF_WORDS]; + bn_word r2[2][RSA_HALF_WORDS]; + bn_word m[2][RSA_HALF_WORDS]; + bn_word x[RSA_HALF_WORDS]; + bn_word one[RSA_HALF_WORDS]; + bn_word one_m[RSA_HALF_WORDS]; + bn_word qi[RSA_HALF_WORDS]; + bn_word tab[16 * RSA_HALF_WORDS]; + bn_word tmp[BN_MONT_TMP_WORDS(RSA_HALF_WORDS)]; + uint8_t em[BN_MAX_WORDS * BN_WORD_BYTES]; +}; + +/* m = c^d mod p for one CRT half: c (kn words) is reduced to p first. */ +static int rsa_crt_half(struct rsa_sign_work *w, unsigned h, + const uint8_t *d, size_t d_len, unsigned kn, unsigned kp) +{ + const bn_word *p = w->prime[h]; + bn_word n0 = bn_mont_n0(p); + unsigned dk = (unsigned)((d_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + + if (dk > kp) + return -1; + bn_mont_r2(w->r2[h], p, kp); + bn_zero(w->one, kp); + w->one[0] = 1; + bn_mont_mul(w->one_m, w->r2[h], w->one, p, n0, kp, w->tmp); + + /* c < n < p * R: one reduction gives c R^-1, two products c R. */ + bn_zero(w->t, 2 * kp); + bn_copy(w->t, w->c, kn); + bn_mont_reduce(w->x, w->t, p, n0, kp); + bn_mont_mul(w->x, w->x, w->r2[h], p, n0, kp, w->tmp); + bn_mont_mul(w->x, w->x, w->r2[h], p, n0, kp, w->tmp); + + bn_zero(w->t, kp); + bn_from_be(w->t, kp, d, d_len); + bn_mont_exp_ct(w->m[h], w->x, w->t, kp * BN_WORD_BITS, w->one_m, + p, n0, kp, w->tab, w->tmp); + bn_mont_mul(w->m[h], w->m[h], w->one, p, n0, kp, w->tmp); + return 0; +} + +static void bn_mul_plain(bn_word *r, const bn_word *a, const bn_word *b, + unsigned k) +{ + unsigned i, j; + bn_zero(r, 2 * k); + for (i = 0; i < k; i++) + { + bn_dword carry = 0; + for (j = 0; j < k; j++) + { + carry += (bn_dword)r[i + j] + (bn_dword)a[j] * b[i]; + r[i + j] = (bn_word)carry; + carry >>= BN_WORD_BITS; + } + r[i + k] = (bn_word)carry; + } +} + +/* Strips the leading zero octets of a big-endian integer. */ +static void rsa_strip(const uint8_t **v, size_t *len) +{ + while (*len && (*v)[0] == 0) + { + (*v)++; + (*len)--; + } +} + +int rsa_pkcs1_sign(const struct rsa_private_key *key, + enum rsa_hash hash, const uint8_t *digest, size_t digest_len, + uint8_t *sig, size_t sig_len) +{ + const uint8_t *di, *n, *e, *p, *q, *dp, *dq, *qi; + size_t di_len, hlen, n_len, e_len, p_len, q_len, dp_len, dq_len, qi_len; + unsigned kn, kp, i; + bn_word carry; + struct rsa_sign_work *w; + int ret = -1; + + if (rsa_pkcs1_hash(hash, &di, &di_len, &hlen) != 0 || digest_len != hlen) + return -1; + n = key->n; n_len = key->n_len; rsa_strip(&n, &n_len); + e = key->e; e_len = key->e_len; rsa_strip(&e, &e_len); + p = key->p; p_len = key->p_len; rsa_strip(&p, &p_len); + q = key->q; q_len = key->q_len; rsa_strip(&q, &q_len); + dp = key->dp; dp_len = key->dp_len; rsa_strip(&dp, &dp_len); + dq = key->dq; dq_len = key->dq_len; rsa_strip(&dq, &dq_len); + qi = key->qi; qi_len = key->qi_len; rsa_strip(&qi, &qi_len); + + if (n_len < 64 || n_len > BN_MAX_WORDS * BN_WORD_BYTES || !(n[n_len - 1] & 1)) + return -1; + if (!e_len || e_len > n_len || !p_len || !q_len || !(p[p_len - 1] & 1) || !(q[q_len - 1] & 1)) + return -1; + if (sig_len != n_len || n_len < di_len + hlen + 11) + return -1; + /* The CRT halves share one word count, at least half the modulus'. */ + kn = (unsigned)((n_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + kp = (unsigned)((p_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES); + if (kp != (unsigned)((q_len + BN_WORD_BYTES - 1) / BN_WORD_BYTES) + || kp > RSA_HALF_WORDS || 2 * kp < kn || qi_len > p_len) + return -1; + + if (!(w = (struct rsa_sign_work*)malloc(sizeof(*w)))) + return -1; + + rsa_pkcs1_encode(w->em, n_len, di, di_len, digest, hlen); + bn_from_be(w->c, kn, w->em, n_len); + bn_from_be(w->prime[0], kp, p, p_len); + bn_from_be(w->prime[1], kp, q, q_len); + bn_from_be(w->qi, kp, qi, qi_len); + + if (bn_cmp(w->qi, w->prime[0], kp) >= 0 + || rsa_crt_half(w, 0, dp, dp_len, kn, kp) != 0 + || rsa_crt_half(w, 1, dq, dq_len, kn, kp) != 0) + goto done; + + /* h = qi (m1 - m2) mod p, with m2 < q brought below p first; + * s = m2 + h q. */ + { + const bn_word *pr = w->prime[0]; + bn_word n0 = bn_mont_n0(pr); + bn_zero(w->one, kp); + w->one[0] = 1; + bn_mont_mul(w->x, w->m[1], w->r2[0], pr, n0, kp, w->tmp); + bn_mont_mul(w->x, w->x, w->one, pr, n0, kp, w->tmp); + bn_mod_sub(w->x, w->m[0], w->x, pr, kp); + bn_mod_mul(w->x, w->qi, w->x, pr, w->r2[0], n0, kp, w->tmp); + } + bn_mul_plain(w->s, w->x, w->prime[1], kp); + carry = 0; + for (i = 0; i < 2 * kp; i++) + { + bn_dword t = (bn_dword)w->s[i] + carry + (i < kp ? w->m[1][i] : 0); + w->s[i] = (bn_word)t; + carry = (bn_word)(t >> BN_WORD_BITS); + } + bn_to_be(sig, n_len, w->s, kn); + + /* A bad CRT parameter or a fault gives a signature that does not + * verify, and one that leaks the factors: it is not handed out. */ + if (rsa_pkcs1_verify(n, n_len, e, e_len, hash, digest, digest_len, + sig, sig_len) == 0) + ret = 0; + else + memset(sig, 0, sig_len); + +done: + crypto_memzero(w, sizeof(*w)); + free(w); + return ret; +} + +/* ---- sect233r1 (NIST B-233) ------------------------------------------ */ + +/* y^2 + xy = x^3 + x^2 + b over GF(2^233), f = z^233 + z^74 + 1, + * cofactor 2. Field elements are eight little-endian 32-bit words. */ +#define B233_W 8 +#define B233_K (256 / BN_WORD_BITS) + +typedef uint32_t b233_fe[B233_W]; + +static const uint8_t b233_b_be[30] = { + 0x00,0x66,0x64,0x7e,0xde,0x6c,0x33,0x2c,0x7f,0x8c,0x09,0x23,0xbb,0x58,0x21, + 0x3b,0x33,0x3b,0x20,0xe9,0xce,0x42,0x81,0xfe,0x11,0x5f,0x7d,0x8f,0x90,0xad +}; +static const uint8_t b233_gx_be[30] = { + 0x00,0xfa,0xc9,0xdf,0xcb,0xac,0x83,0x13,0xbb,0x21,0x39,0xf1,0xbb,0x75,0x5f, + 0xef,0x65,0xbc,0x39,0x1f,0x8b,0x36,0xf8,0xf8,0xeb,0x73,0x71,0xfd,0x55,0x8b +}; +static const uint8_t b233_gy_be[30] = { + 0x01,0x00,0x6a,0x08,0xa4,0x19,0x03,0x35,0x06,0x78,0xe5,0x85,0x28,0xbe,0xbf, + 0x8a,0x0b,0xef,0xf8,0x67,0xa7,0xca,0x36,0x71,0x6f,0x7e,0x01,0xf8,0x10,0x52 +}; +static const uint8_t b233_n_be[30] = { + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x13,0xe9,0x74,0xe7,0x2f,0x8a,0x69,0x22,0x03,0x1d,0x26,0x03,0xcf,0xe0,0xd7 +}; + +static void b233_from_be(b233_fe r, const uint8_t *in) +{ + unsigned i; + memset(r, 0, sizeof(b233_fe)); + for (i = 0; i < 30; i++) + r[(29 - i) / 4] |= (uint32_t)in[i] << (8 * ((29 - i) & 3)); +} + +static void b233_to_be(uint8_t *out, const b233_fe a) +{ + unsigned i; + for (i = 0; i < 30; i++) + out[i] = (uint8_t)(a[(29 - i) / 4] >> (8 * ((29 - i) & 3))); +} + +static int b233_is_zero(const b233_fe a) +{ + uint32_t v = 0; + unsigned i; + for (i = 0; i < B233_W; i++) + v |= a[i]; + return v == 0; +} + +static int b233_eq(const b233_fe a, const b233_fe b) +{ + uint32_t v = 0; + unsigned i; + for (i = 0; i < B233_W; i++) + v |= a[i] ^ b[i]; + return v == 0; +} + +static void b233_add(b233_fe r, const b233_fe a, const b233_fe b) +{ + unsigned i; + for (i = 0; i < B233_W; i++) + r[i] = a[i] ^ b[i]; +} + +/* c (sixteen words, clobbered) mod f into r. */ +static void b233_reduce(b233_fe r, uint32_t *c) +{ + uint32_t t; + int i; + for (i = 15; i >= 8; i--) + { + t = c[i]; + c[i - 8] ^= t << 23; + c[i - 7] ^= t >> 9; + c[i - 5] ^= t << 1; + c[i - 4] ^= t >> 31; + } + t = c[7] >> 9; + c[0] ^= t; + c[2] ^= t << 10; + c[3] ^= t >> 22; + c[7] &= 0x1ff; + memcpy(r, c, sizeof(b233_fe)); +} + +/* Carry-less 32x32 product, without operand-dependent branches or + * memory accesses. */ +static uint64_t b233_clmul32(uint32_t a, uint32_t b) +{ + uint64_t r = 0; + uint64_t aa = a; + unsigned i; + for (i = 0; i < 32; i++) + r ^= (aa << i) & ((uint64_t)0 - ((b >> i) & 1)); + return r; +} + +static void b233_mul_sw(b233_fe r, const b233_fe a, const b233_fe b) +{ + uint32_t c[16]; + unsigned i, j; + memset(c, 0, sizeof(c)); + for (i = 0; i < B233_W; i++) + for (j = 0; j < B233_W; j++) + { + uint64_t t = b233_clmul32(a[i], b[j]); + c[i + j] ^= (uint32_t)t; + c[i + j + 1] ^= (uint32_t)(t >> 32); + } + b233_reduce(r, c); +} + +/* The field product on the carry-less multiply instructions, four + * 64-bit limbs a side, where the CPU has them: PCLMULQDQ, or PMULL with + * the ARMv8 crypto extension. Chosen at run time as crypto.c chooses its + * AES and GHASH paths. */ +#if (defined(__x86_64__) || defined(__i386__)) && !defined(_MSC_VER) && !defined(B233_NO_X86) +#if defined(__has_attribute) && defined(__has_include) +#if __has_attribute(target) && __has_include() \ + && (!defined(__SCE__) || defined(__PCLMUL__)) +#define B233_HAVE_X86_PATH 1 +#endif +#endif +#endif + +#if (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_MSC_VER) && !defined(B233_NO_ARM) +#if defined(__ARM_FEATURE_CRYPTO) || defined(__ARM_FEATURE_AES) +#define B233_HAVE_ARM_PATH 1 +#elif (defined(__clang__) && __clang_major__ >= 16) \ + || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 9) +#define B233_HAVE_ARM_PATH 1 +#define B233_ARM_NEEDS_TARGET 1 +#endif +#endif + +#if defined(B233_HAVE_X86_PATH) || defined(B233_HAVE_ARM_PATH) +#include + +static retro_atomic_int_t b233_clmul_ok; + +static int b233_clmul_available(void) +{ + int v = retro_atomic_load_acquire_int(&b233_clmul_ok); + if (!v) + { + uint64_t f = cpu_features_get(); +#if defined(B233_HAVE_X86_PATH) + v = (f & RETRO_SIMD_PCLMUL) ? 2 : 1; +#elif defined(B233_ARM_NEEDS_TARGET) + v = (f & RETRO_SIMD_AES) ? 2 : 1; +#else + (void)f; + v = 2; +#endif + retro_atomic_store_release_int(&b233_clmul_ok, v); + } + return v == 2; +} + +/* c[0..15] = the unreduced product, from 64 x 64 products. */ +#if defined(B233_HAVE_X86_PATH) +#include + +__attribute__((target("pclmul,sse2"))) +static void b233_mul_hw(b233_fe r, const b233_fe a, const b233_fe b) +{ + uint64_t c64[8]; + uint32_t c[16]; + __m128i av[4], bv[4]; + unsigned i, j; + + for (i = 0; i < 4; i++) + { + av[i] = _mm_set_epi32(0, 0, (int)a[2 * i + 1], (int)a[2 * i]); + bv[i] = _mm_set_epi32(0, 0, (int)b[2 * i + 1], (int)b[2 * i]); + } + memset(c64, 0, sizeof(c64)); + for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) + { + uint64_t t[2]; + _mm_storeu_si128((__m128i*)t, _mm_clmulepi64_si128(av[i], bv[j], 0x00)); + c64[i + j] ^= t[0]; + c64[i + j + 1] ^= t[1]; + } + for (i = 0; i < 8; i++) + { + c[2 * i] = (uint32_t)c64[i]; + c[2 * i + 1] = (uint32_t)(c64[i] >> 32); + } + b233_reduce(r, c); +} +#else +#include + +#if defined(B233_ARM_NEEDS_TARGET) +__attribute__((target("+crypto"))) +#endif +static void b233_mul_hw(b233_fe r, const b233_fe a, const b233_fe b) +{ + uint64_t c64[8]; + uint32_t c[16]; + uint64_t av[4], bv[4]; + unsigned i, j; + + for (i = 0; i < 4; i++) + { + av[i] = a[2 * i] | ((uint64_t)a[2 * i + 1] << 32); + bv[i] = b[2 * i] | ((uint64_t)b[2 * i + 1] << 32); + } + memset(c64, 0, sizeof(c64)); + for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) + { + uint64x2_t t = vreinterpretq_u64_p128(vmull_p64((poly64_t)av[i], (poly64_t)bv[j])); + c64[i + j] ^= vgetq_lane_u64(t, 0); + c64[i + j + 1] ^= vgetq_lane_u64(t, 1); + } + for (i = 0; i < 8; i++) + { + c[2 * i] = (uint32_t)c64[i]; + c[2 * i + 1] = (uint32_t)(c64[i] >> 32); + } + b233_reduce(r, c); +} +#endif +#endif + +static void b233_mul(b233_fe r, const b233_fe a, const b233_fe b) +{ +#if defined(B233_HAVE_X86_PATH) || defined(B233_HAVE_ARM_PATH) + if (b233_clmul_available()) + { + b233_mul_hw(r, a, b); + return; + } +#endif + b233_mul_sw(r, a, b); +} + +/* The low sixteen bits of x with a zero inserted above each. */ +static uint32_t b233_spread16(uint32_t x) +{ + x &= 0xffff; + x = (x | (x << 8)) & 0x00ff00ffu; + x = (x | (x << 4)) & 0x0f0f0f0fu; + x = (x | (x << 2)) & 0x33333333u; + x = (x | (x << 1)) & 0x55555555u; + return x; +} + +static void b233_sqr(b233_fe r, const b233_fe a) +{ + uint32_t c[16]; + unsigned i; + for (i = 0; i < B233_W; i++) + { + c[2 * i] = b233_spread16(a[i]); + c[2 * i + 1] = b233_spread16(a[i] >> 16); + } + b233_reduce(r, c); +} + +static void b233_sqrn(b233_fe r, const b233_fe a, unsigned n) +{ + memcpy(r, a, sizeof(b233_fe)); + while (n--) + b233_sqr(r, r); +} + +/* a^(2^233 - 2) by Itoh-Tsujii: t_k = a^(2^k - 1) along + * 1, 2, 4, 8, 16, 32, 64, 128, 192, 224, 232. */ +static void b233_inv(b233_fe r, const b233_fe a) +{ + b233_fe t1, t2, t4, t8, t32, t64, t, u; + + memcpy(t1, a, sizeof(b233_fe)); + b233_sqr(t, t1); b233_mul(t2, t, t1); + b233_sqrn(t, t2, 2); b233_mul(t4, t, t2); + b233_sqrn(t, t4, 4); b233_mul(t8, t, t4); + b233_sqrn(t, t8, 8); b233_mul(u, t, t8); /* 16 */ + b233_sqrn(t, u, 16); b233_mul(t32, t, u); + b233_sqrn(t, t32, 32); b233_mul(t64, t, t32); + b233_sqrn(t, t64, 64); b233_mul(u, t, t64); /* 128 */ + b233_sqrn(t, u, 64); b233_mul(u, t, t64); /* 192 */ + b233_sqrn(t, u, 32); b233_mul(u, t, t32); /* 224 */ + b233_sqrn(t, u, 8); b233_mul(u, t, t8); /* 232 */ + b233_sqr(r, u); +} + +/* Tr(a) = a + a^2 + ... + a^(2^232), which is 0 or 1. */ +static unsigned b233_trace(const b233_fe a) +{ + b233_fe t, s; + unsigned i; + memcpy(t, a, sizeof(b233_fe)); + memcpy(s, a, sizeof(b233_fe)); + for (i = 1; i < 233; i++) + { + b233_sqr(t, t); + b233_add(s, s, t); + } + return s[0] & 1; +} + +static void b233_cswap(uint32_t bit, b233_fe a, b233_fe b) +{ + uint32_t mask = (uint32_t)0 - bit; + unsigned i; + for (i = 0; i < B233_W; i++) + { + uint32_t t = mask & (a[i] ^ b[i]); + a[i] ^= t; + b[i] ^= t; + } +} + +/* Parse 0x04 || X || Y, the coordinates below 2^233, and check the + * curve equation and that the point lies in the subgroup of order n: + * with h = 2, those are the points 2Q, which are the ones with + * Tr(x) = Tr(a) = 1. That also rules out x = 0. */ +static int b233_pt_load(b233_fe x, b233_fe y, const uint8_t *pub) +{ + b233_fe b, lhs, rhs, t; + + if (pub[0] != 0x04 || pub[1] > 1 || pub[31] > 1) + return -1; + b233_from_be(x, pub + 1); + b233_from_be(y, pub + 31); + b233_from_be(b, b233_b_be); + + b233_sqr(lhs, y); + b233_mul(t, x, y); + b233_add(lhs, lhs, t); + b233_sqr(t, x); + b233_mul(rhs, t, x); + b233_add(rhs, rhs, t); + b233_add(rhs, rhs, b); + if (!b233_eq(lhs, rhs)) + return -1; + return b233_trace(x) == 1 ? 0 : -1; +} + +/* Scalar-side constants, the order n with its Montgomery machinery. */ +struct b233_order +{ + bn_word n[B233_K]; + bn_word r2[B233_K]; + bn_word one_m[B233_K]; + bn_word n0; +}; + +static void b233_order_init(struct b233_order *o) +{ + bn_word one[B233_K]; + bn_word tmp[BN_MONT_TMP_WORDS(B233_K)]; + bn_from_be(o->n, B233_K, b233_n_be, sizeof(b233_n_be)); + o->n0 = bn_mont_n0(o->n); + bn_mont_r2(o->r2, o->n, B233_K); + bn_zero(one, B233_K); + one[0] = 1; + bn_mont_mul(o->one_m, o->r2, one, o->n, o->n0, B233_K, tmp); +} + +/* k = a mod n for a below 2^256: a * R^2 * R^-1 then * 1 * R^-1. */ +static void b233_mod_n(const struct b233_order *o, bn_word *k, const bn_word *a) +{ + bn_word one[B233_K]; + bn_word tmp[BN_MONT_TMP_WORDS(B233_K)]; + bn_zero(one, B233_K); + one[0] = 1; + bn_mont_mul(k, a, o->r2, o->n, o->n0, B233_K, tmp); + bn_mont_mul(k, k, one, o->n, o->n0, B233_K, tmp); +} + +/* A private key, any 30 octets, reduced mod n. -1 when that is 0. */ +static int b233_scalar_load(const struct b233_order *o, bn_word *k, const uint8_t *s) +{ + bn_word a[B233_K]; + bn_from_be(a, B233_K, s, B233_SCALAR_SIZE); + b233_mod_n(o, k, a); + crypto_memzero(a, sizeof(a)); + return bn_is_zero(k, B233_K) ? -1 : 0; +} + +/* kP for k < n through the Lopez-Dahab Montgomery ladder on X/Z, which + * does the same field operations for every bit. k is taken as k + n or + * k + 2n, whichever has bit 233 as its top bit, so the ladder length is + * fixed too; nP = O for the points b233_pt_load() accepts. y is + * recovered from the two ladder points at the end (Lopez and Dahab, + * CHES 1999). @y may be NULL. Returns -1 when kP is infinity. */ +static int b233_pt_mul(const struct b233_order *o, b233_fe xr, b233_fe yr, + const bn_word *k, const b233_fe x, const b233_fe y) +{ + bn_word k1[B233_K], k2[B233_K]; + b233_fe b, x1, z1, x2, z2, t1, t2, t3; + int i; + int ret = 0; + + bn_add(k1, k, o->n, B233_K); + bn_add(k2, k1, o->n, B233_K); + bn_select(k1, (bn_word)bn_get_bit(k1, 233), k1, k2, B233_K); + + b233_from_be(b, b233_b_be); + memcpy(x1, x, sizeof(b233_fe)); + memset(z1, 0, sizeof(b233_fe)); + z1[0] = 1; + b233_sqr(z2, x); + b233_sqr(x2, z2); + b233_add(x2, x2, b); + + for (i = 232; i >= 0; i--) + { + uint32_t bit = (uint32_t)bn_get_bit(k1, (unsigned)i); + b233_cswap(bit, x1, x2); + b233_cswap(bit, z1, z2); + /* (x2, z2) = (x1, z1) + (x2, z2), their difference being P */ + b233_mul(t1, x1, z2); + b233_mul(t2, x2, z1); + b233_add(t3, t1, t2); + b233_sqr(z2, t3); + b233_mul(t1, t1, t2); + b233_mul(x2, x, z2); + b233_add(x2, x2, t1); + /* (x1, z1) = 2 (x1, z1) */ + b233_sqr(t1, x1); + b233_sqr(t2, z1); + b233_mul(z1, t1, t2); + b233_sqr(t1, t1); + b233_sqr(t2, t2); + b233_mul(t2, t2, b); + b233_add(x1, t1, t2); + b233_cswap(bit, x1, x2); + b233_cswap(bit, z1, z2); + } + + if (b233_is_zero(z1)) + ret = -1; + else if (b233_is_zero(z2)) + { + /* (k + 1)P = O: kP = -P */ + memcpy(xr, x, sizeof(b233_fe)); + if (yr) + b233_add(yr, x, y); + } + else if (!yr) + { + b233_inv(t1, z1); + b233_mul(xr, x1, t1); + } + else + { + /* t3 = 1 / (x z1 z2), x3 = x1 x z2 t3, + * y3 = (x + x3) ((x1 + x z1)(x2 + x z2) + (x^2 + y) z1 z2) t3 + y */ + b233_fe zz, x3; + b233_mul(zz, z1, z2); + b233_mul(t3, x, zz); + b233_inv(t3, t3); + b233_mul(x3, x1, z2); + b233_mul(x3, x3, x); + b233_mul(x3, x3, t3); + b233_mul(t1, x, z1); + b233_add(t1, t1, x1); + b233_mul(t2, x, z2); + b233_add(t2, t2, x2); + b233_mul(t1, t1, t2); + b233_sqr(t2, x); + b233_add(t2, t2, y); + b233_mul(t2, t2, zz); + b233_add(t1, t1, t2); + b233_add(t2, x, x3); + b233_mul(t1, t1, t2); + b233_mul(t1, t1, t3); + b233_add(yr, t1, y); + memcpy(xr, x3, sizeof(b233_fe)); + crypto_memzero(zz, sizeof(zz)); + crypto_memzero(x3, sizeof(x3)); + } + + crypto_memzero(k1, sizeof(k1)); + crypto_memzero(k2, sizeof(k2)); + crypto_memzero(x1, sizeof(x1)); + crypto_memzero(z1, sizeof(z1)); + crypto_memzero(x2, sizeof(x2)); + crypto_memzero(z2, sizeof(z2)); + crypto_memzero(t1, sizeof(t1)); + crypto_memzero(t2, sizeof(t2)); + return ret; +} + +static void b233_base(b233_fe gx, b233_fe gy) +{ + b233_from_be(gx, b233_gx_be); + b233_from_be(gy, b233_gy_be); +} + +/* An x coordinate as an integer mod n: x < 2^233 < 2n. */ +static void b233_x_mod_n(const struct b233_order *o, bn_word *r, const b233_fe x) +{ + uint8_t be[30]; + b233_to_be(be, x); + bn_from_be(r, B233_K, be, sizeof(be)); + if (bn_cmp(r, o->n, B233_K) >= 0) + bn_sub(r, r, o->n, B233_K); +} + +/* The leftmost 233 bits of a digest as an integer, mod n (FIPS 186-4 + * 6.4, and bits2octets of RFC 6979). */ +static void b233_digest_load(const struct b233_order *o, bn_word *e, + const uint8_t *digest, size_t len) +{ + size_t m = len > 30 ? 30 : len; + unsigned i; + bn_from_be(e, B233_K, digest, m); + if (m == 30) + { + for (i = 0; i < B233_K - 1; i++) + e[i] = (e[i] >> 7) | (e[i + 1] << (BN_WORD_BITS - 7)); + e[B233_K - 1] >>= 7; + } + if (bn_cmp(e, o->n, B233_K) >= 0) + bn_sub(e, e, o->n, B233_K); +} + +int b233_keygen(const uint8_t *priv, uint8_t *pub) +{ + struct b233_order o; + bn_word k[B233_K]; + b233_fe gx, gy, x, y; + int ret; + + b233_order_init(&o); + if (b233_scalar_load(&o, k, priv) != 0) + return -1; + b233_base(gx, gy); + ret = b233_pt_mul(&o, x, y, k, gx, gy); + crypto_memzero(k, sizeof(k)); + if (ret != 0) + return -1; + pub[0] = 0x04; + b233_to_be(pub + 1, x); + b233_to_be(pub + 31, y); + return 0; +} + +int b233_ecdh(const uint8_t *priv, const uint8_t *peer, uint8_t *shared) +{ + struct b233_order o; + bn_word k[B233_K]; + b233_fe px, py, x; + int ret; + + b233_order_init(&o); + if (b233_pt_load(px, py, peer) != 0 || b233_scalar_load(&o, k, priv) != 0) + return -1; + ret = b233_pt_mul(&o, x, NULL, k, px, py); + crypto_memzero(k, sizeof(k)); + if (ret != 0) + return -1; + b233_to_be(shared, x); + crypto_memzero(x, sizeof(x)); + return 0; +} + +/* RFC 6979 3.2 with HMAC-SHA256: K and V, then candidates one by one. */ +struct b233_nonce +{ + uint8_t k[32]; + uint8_t v[32]; +}; + +static void b233_nonce_hmac(struct b233_nonce *st, int with_sep, uint8_t sep, + const uint8_t *x, const uint8_t *h) +{ + struct hmac_sha256_ctx ctx; + if (with_sep) + { + hmac_sha256_init(&ctx, st->k, 32); + hmac_sha256_update(&ctx, st->v, 32); + hmac_sha256_update(&ctx, &sep, 1); + if (x) + { + hmac_sha256_update(&ctx, x, 30); + hmac_sha256_update(&ctx, h, 30); + } + hmac_sha256_final(&ctx, st->k); + } + hmac_sha256(st->k, 32, st->v, 32, st->v); +} + +static void b233_nonce_init(struct b233_nonce *st, + const bn_word *d, const bn_word *e) +{ + uint8_t x[30], h[30]; + bn_to_be(x, sizeof(x), d, B233_K); + bn_to_be(h, sizeof(h), e, B233_K); + memset(st->v, 0x01, sizeof(st->v)); + memset(st->k, 0x00, sizeof(st->k)); + b233_nonce_hmac(st, 1, 0x00, x, h); + b233_nonce_hmac(st, 1, 0x01, x, h); + crypto_memzero(x, sizeof(x)); +} + +/* The next candidate k in [1, n), drawing again past one out of range. */ +static void b233_nonce_next(struct b233_nonce *st, const struct b233_order *o, + bn_word *k, int retry) +{ + for (;;) + { + if (retry) + b233_nonce_hmac(st, 1, 0x00, NULL, NULL); + retry = 1; + b233_nonce_hmac(st, 0, 0, NULL, NULL); + bn_from_be(k, B233_K, st->v, 30); + { + unsigned i; + for (i = 0; i < B233_K - 1; i++) + k[i] = (k[i] >> 7) | (k[i + 1] << (BN_WORD_BITS - 7)); + k[B233_K - 1] >>= 7; + } + if (!bn_is_zero(k, B233_K) && bn_cmp(k, o->n, B233_K) < 0) + return; + } +} + +/* s = k^-1 (e + r d) mod n; k^-1 = k^(n - 2). */ +static void b233_sign_s(const struct b233_order *o, bn_word *s, + const bn_word *k, const bn_word *e, const bn_word *r, const bn_word *d) +{ + bn_word tab[16 * B233_K]; + bn_word tmp[BN_MONT_TMP_WORDS(B233_K)]; + bn_word km[B233_K], nm2[B233_K], t[B233_K]; + + bn_zero(t, B233_K); + t[0] = 2; + bn_sub(nm2, o->n, t, B233_K); + bn_mont_mul(km, k, o->r2, o->n, o->n0, B233_K, tmp); + bn_mont_exp_ct(km, km, nm2, 233, o->one_m, o->n, o->n0, B233_K, tab, tmp); + bn_mod_mul(t, r, d, o->n, o->r2, o->n0, B233_K, tmp); + bn_mod_add(t, t, e, o->n, B233_K); + bn_mont_mul(s, km, t, o->n, o->n0, B233_K, tmp); + crypto_memzero(tab, sizeof(tab)); + crypto_memzero(km, sizeof(km)); + crypto_memzero(t, sizeof(t)); +} + +int b233_ecdsa_sign(const uint8_t *priv, + const uint8_t *digest, size_t digest_len, + uint8_t *r, uint8_t *s) +{ + struct b233_order o; + struct b233_nonce st; + bn_word d[B233_K], e[B233_K], k[B233_K], rr[B233_K], ss[B233_K]; + b233_fe gx, gy, x; + int retry = 0; + + b233_order_init(&o); + if (b233_scalar_load(&o, d, priv) != 0) + return -1; + b233_digest_load(&o, e, digest, digest_len); + b233_base(gx, gy); + b233_nonce_init(&st, d, e); + for (;;) + { + b233_nonce_next(&st, &o, k, retry); + retry = 1; + if (b233_pt_mul(&o, x, NULL, k, gx, gy) != 0) + continue; + b233_x_mod_n(&o, rr, x); + if (bn_is_zero(rr, B233_K)) + continue; + b233_sign_s(&o, ss, k, e, rr, d); + if (!bn_is_zero(ss, B233_K)) + break; + } + bn_to_be(r, B233_SCALAR_SIZE, rr, B233_K); + bn_to_be(s, B233_SCALAR_SIZE, ss, B233_K); + crypto_memzero(&st, sizeof(st)); + crypto_memzero(d, sizeof(d)); + crypto_memzero(k, sizeof(k)); + return 0; +} + +/* Affine sum of two points for signature checks, where everything is + * public. A point at infinity is flagged rather than encoded. */ +static void b233_pt_add_affine(b233_fe x3, b233_fe y3, int *inf3, + const b233_fe x1, const b233_fe y1, int inf1, + const b233_fe x2, const b233_fe y2, int inf2) +{ + b233_fe l, t, xs, ys; + + *inf3 = 0; + if (inf1 || inf2) + { + *inf3 = inf1 && inf2; + memcpy(x3, inf1 ? x2 : x1, sizeof(b233_fe)); + memcpy(y3, inf1 ? y2 : y1, sizeof(b233_fe)); + return; + } + if (b233_eq(x1, x2)) + { + if (!b233_eq(y1, y2) || b233_is_zero(x1)) + { + *inf3 = 1; + return; + } + /* l = x + y / x, x3 = l^2 + l + 1, y3 = x^2 + (l + 1) x3 */ + b233_inv(t, x1); + b233_mul(l, y1, t); + b233_add(l, l, x1); + b233_sqr(xs, l); + b233_add(xs, xs, l); + xs[0] ^= 1; + l[0] ^= 1; + b233_mul(ys, l, xs); + b233_sqr(t, x1); + b233_add(ys, ys, t); + } + else + { + /* l = (y1 + y2) / (x1 + x2), x3 = l^2 + l + x1 + x2 + 1, + * y3 = l (x1 + x3) + x3 + y1 */ + b233_add(t, x1, x2); + b233_inv(t, t); + b233_add(l, y1, y2); + b233_mul(l, l, t); + b233_sqr(xs, l); + b233_add(xs, xs, l); + b233_add(xs, xs, x1); + b233_add(xs, xs, x2); + xs[0] ^= 1; + b233_add(t, x1, xs); + b233_mul(ys, l, t); + b233_add(ys, ys, xs); + b233_add(ys, ys, y1); + } + memcpy(x3, xs, sizeof(b233_fe)); + memcpy(y3, ys, sizeof(b233_fe)); +} + +int b233_ecdsa_verify(const uint8_t *pub, + const uint8_t *digest, size_t digest_len, + const uint8_t *r, const uint8_t *s) +{ + struct b233_order o; + bn_word rr[B233_K], ss[B233_K], e[B233_K], w[B233_K], u[B233_K], v[B233_K]; + bn_word work[3 * B233_K + BN_MONT_TMP_WORDS(B233_K)]; + bn_word one[B233_K]; + b233_fe qx, qy, gx, gy, ax, ay, bx, by; + int ainf, binf, inf; + + b233_order_init(&o); + if (b233_pt_load(qx, qy, pub) != 0) + return -1; + bn_from_be(rr, B233_K, r, B233_SCALAR_SIZE); + bn_from_be(ss, B233_K, s, B233_SCALAR_SIZE); + if (bn_is_zero(rr, B233_K) || bn_cmp(rr, o.n, B233_K) >= 0 + || bn_is_zero(ss, B233_K) || bn_cmp(ss, o.n, B233_K) >= 0) + return -1; + b233_digest_load(&o, e, digest, digest_len); + + /* w = s^-1 = s^(n - 2) mod n */ + bn_zero(one, B233_K); + one[0] = 2; + bn_sub(v, o.n, one, B233_K); + bn_mod_exp(w, ss, v, B233_K, o.n, o.r2, o.n0, B233_K, work); + + b233_base(gx, gy); + bn_mod_mul(u, e, w, o.n, o.r2, o.n0, B233_K, work); + ainf = b233_pt_mul(&o, ax, ay, u, gx, gy) != 0; + bn_mod_mul(u, rr, w, o.n, o.r2, o.n0, B233_K, work); + binf = b233_pt_mul(&o, bx, by, u, qx, qy) != 0; + b233_pt_add_affine(ax, ay, &inf, ax, ay, ainf, bx, by, binf); + if (inf) + return -1; + b233_x_mod_n(&o, v, ax); + return bn_cmp(v, rr, B233_K) == 0 ? 0 : -1; +} diff --git a/dependencies/libretro-common/crypto/x509.c b/dependencies/libretro-common/crypto/x509.c new file mode 100644 index 00000000..46ac2cde --- /dev/null +++ b/dependencies/libretro-common/crypto/x509.c @@ -0,0 +1,1475 @@ +/* Copyright (C) 2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (x509.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* ---- DER ---------------------------------------------------------- */ + +#define DER_INTEGER 0x02 +#define DER_BITSTRING 0x03 +#define DER_OCTET 0x04 +#define DER_NULL 0x05 +#define DER_OID 0x06 +#define DER_UTF8 0x0c +#define DER_PRINTABLE 0x13 +#define DER_IA5 0x16 +#define DER_UTCTIME 0x17 +#define DER_GENTIME 0x18 +#define DER_SEQUENCE 0x30 +#define DER_SET 0x31 +#define DER_CTX(n) (0xa0 | (n)) + +struct der +{ + const uint8_t *p; + const uint8_t *end; +}; + +/* Reads one TLV at *d. On success *d advances past it, @val/@val_len + * cover the contents. Definite lengths only, no indefinite forms. */ +static int der_read(struct der *d, uint8_t tag, + const uint8_t **val, size_t *val_len) +{ + const uint8_t *p = d->p; + size_t len; + + if (p >= d->end || *p != tag) + return -1; + p++; + if (p >= d->end) + return -1; + if (*p < 0x80) + len = *p++; + else + { + unsigned n = *p++ & 0x7f; + if (n == 0 || n > 4 || p + n > d->end) + return -1; + len = 0; + while (n--) + len = (len << 8) | *p++; + if (len < 0x80) + return -1; /* not minimal: not DER */ + } + if (len > (size_t)(d->end - p)) + return -1; + *val = p; + *val_len = len; + d->p = p + len; + return 0; +} + +static int der_peek(const struct der *d, uint8_t tag) +{ + return d->p < d->end && *d->p == tag; +} + +/* Enters a constructed element: @inner spans its contents. */ +static int der_enter(struct der *d, uint8_t tag, struct der *inner) +{ + const uint8_t *v; + size_t vl; + if (der_read(d, tag, &v, &vl) != 0) + return -1; + inner->p = v; + inner->end = v + vl; + return 0; +} + +/* Reads an INTEGER as an unsigned big-endian octet string with the + * leading zero of a positive value stripped. */ +static int der_read_uint(struct der *d, const uint8_t **val, size_t *val_len) +{ + if (der_read(d, DER_INTEGER, val, val_len) != 0 || *val_len == 0) + return -1; + if (**val & 0x80) + return -1; /* negative */ + while (*val_len > 1 && **val == 0) + { + (*val)++; + (*val_len)--; + } + return 0; +} + +static int der_oid_eq(const uint8_t *v, size_t vl, const uint8_t *oid, size_t ol) +{ + return vl == ol && memcmp(v, oid, ol) == 0; +} + +/* OIDs, DER contents octets. */ +static const uint8_t oid_rsa_enc[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x01}; +static const uint8_t oid_rsa_sha256[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0b}; +static const uint8_t oid_rsa_sha384[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0c}; +static const uint8_t oid_rsa_sha512[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0d}; +static const uint8_t oid_ec_pub[] = {0x2a,0x86,0x48,0xce,0x3d,0x02,0x01}; +static const uint8_t oid_p256[] = {0x2a,0x86,0x48,0xce,0x3d,0x03,0x01,0x07}; +static const uint8_t oid_p384[] = {0x2b,0x81,0x04,0x00,0x22}; +static const uint8_t oid_ecdsa_sha256[] = {0x2a,0x86,0x48,0xce,0x3d,0x04,0x03,0x02}; +static const uint8_t oid_ecdsa_sha384[] = {0x2a,0x86,0x48,0xce,0x3d,0x04,0x03,0x03}; +static const uint8_t oid_ecdsa_sha512[] = {0x2a,0x86,0x48,0xce,0x3d,0x04,0x03,0x04}; +static const uint8_t oid_cn[] = {0x55,0x04,0x03}; +static const uint8_t oid_basic_constr[] = {0x55,0x1d,0x13}; +static const uint8_t oid_key_usage[] = {0x55,0x1d,0x0f}; +static const uint8_t oid_ext_key_usage[] = { 0x55, 0x1d, 0x25 }; /* 2.5.29.37 */ +static const uint8_t oid_eku_server_auth[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 }; /* 1.3.6.1.5.5.7.3.1 */ +static const uint8_t oid_eku_any[] = { 0x55, 0x1d, 0x25, 0x00 }; /* 2.5.29.37.0 */ +static const uint8_t oid_subject_key_id[] = { 0x55, 0x1d, 0x0e }; /* 2.5.29.14 */ +static const uint8_t oid_authority_key_id[] = { 0x55, 0x1d, 0x23 }; /* 2.5.29.35 */ +static const uint8_t oid_cert_policies[] = { 0x55, 0x1d, 0x20 }; /* 2.5.29.32 */ +static const uint8_t oid_crl_dist[] = { 0x55, 0x1d, 0x1f }; /* 2.5.29.31 */ +static const uint8_t oid_auth_info_access[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }; /* 1.3.6.1.5.5.7.1.1 */ +static const uint8_t oid_ct_scts[] = { 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 }; /* 1.3.6.1.4.1.11129.2.4.2 */ +static const uint8_t oid_san[] = {0x55,0x1d,0x11}; + +#define OID_EQ(v, vl, o) der_oid_eq(v, vl, o, sizeof(o)) + +static enum x509_sig_alg x509_sig_alg_from_oid(const uint8_t *v, size_t vl) +{ + if (OID_EQ(v, vl, oid_rsa_sha256)) return X509_SIG_RSA_SHA256; + if (OID_EQ(v, vl, oid_rsa_sha384)) return X509_SIG_RSA_SHA384; + if (OID_EQ(v, vl, oid_rsa_sha512)) return X509_SIG_RSA_SHA512; + if (OID_EQ(v, vl, oid_ecdsa_sha256)) return X509_SIG_ECDSA_SHA256; + if (OID_EQ(v, vl, oid_ecdsa_sha384)) return X509_SIG_ECDSA_SHA384; + if (OID_EQ(v, vl, oid_ecdsa_sha512)) return X509_SIG_ECDSA_SHA512; + return X509_SIG_NONE; +} + +/* AlgorithmIdentifier ::= SEQUENCE { OID, parameters OPTIONAL } */ +static int der_read_alg(struct der *d, const uint8_t **oid, size_t *oid_len, + struct der *params) +{ + struct der a; + if (der_enter(d, DER_SEQUENCE, &a) != 0) + return -1; + if (der_read(&a, DER_OID, oid, oid_len) != 0) + return -1; + *params = a; + return 0; +} + +/* ---- time --------------------------------------------------------- */ + +static int x509_digits(const uint8_t *p, unsigned n, unsigned *out) +{ + unsigned v = 0; + while (n--) + { + if (*p < '0' || *p > '9') + return -1; + v = v * 10 + (unsigned)(*p++ - '0'); + } + *out = v; + return 0; +} + +/* Days since 1970-01-01 for a proleptic Gregorian date. */ +static long x509_days_from_civil(long y, unsigned m, unsigned d) +{ + long era, yoe, doy, doe; + y -= m <= 2; + era = (y >= 0 ? y : y - 399) / 400; + yoe = y - era * 400; + doy = (153 * (long)(m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; +} + +static int der_read_time(struct der *d, time_t *t) +{ + const uint8_t *v; + size_t vl; + unsigned y, mo, da, h, mi, s; + int is_gen; + + if (der_peek(d, DER_UTCTIME)) + { + if (der_read(d, DER_UTCTIME, &v, &vl) != 0 || vl != 13) + return -1; + is_gen = 0; + } + else + { + if (der_read(d, DER_GENTIME, &v, &vl) != 0 || vl != 15) + return -1; + is_gen = 1; + } + if (v[vl - 1] != 'Z') + return -1; + if (is_gen) + { + if (x509_digits(v, 4, &y) != 0) + return -1; + v += 4; + } + else + { + if (x509_digits(v, 2, &y) != 0) + return -1; + y += (y < 50) ? 2000 : 1900; + v += 2; + } + if (x509_digits(v, 2, &mo) || x509_digits(v + 2, 2, &da) + || x509_digits(v + 4, 2, &h) || x509_digits(v + 6, 2, &mi) + || x509_digits(v + 8, 2, &s)) + return -1; + if (mo < 1 || mo > 12 || da < 1 || da > 31 || h > 23 || mi > 59 || s > 60) + return -1; + { + /* long is 32 bits on Windows: past 2038-01-19 the seconds no + * longer fit it, so widen first, and where time_t itself is 32 + * bits saturate rather than wrap a far-future notAfter into the + * past (a leaf good until 2046 must not read as expired). */ + int64_t secs = (int64_t)x509_days_from_civil((long)y, mo, da) * 86400 + + (int64_t)h * 3600 + (int64_t)mi * 60 + (int64_t)s; + if (sizeof(time_t) < 8) + { + if (secs > 0x7fffffff) + secs = 0x7fffffff; + else if (secs < -0x7fffffff - 1) + secs = -0x7fffffff - 1; + } + *t = (time_t)secs; + } + return 0; +} + +/* ---- certificate -------------------------------------------------- */ + +static int x509_parse_spki(struct x509_cert *c, struct der *d) +{ + struct der spki, params; + const uint8_t *oid, *v; + size_t oid_len, vl; + + if (der_enter(d, DER_SEQUENCE, &spki) != 0) + return -1; + if (der_read_alg(&spki, &oid, &oid_len, ¶ms) != 0) + return -1; + if (der_read(&spki, DER_BITSTRING, &v, &vl) != 0 || vl < 2 || v[0] != 0) + return -1; + v++; + vl--; + + if (OID_EQ(oid, oid_len, oid_rsa_enc)) + { + struct der k; + struct der kd; + kd.p = v; + kd.end = v + vl; + if (der_enter(&kd, DER_SEQUENCE, &k) != 0) + return -1; + if (der_read_uint(&k, &c->rsa_n, &c->rsa_n_len) != 0) + return -1; + if (der_read_uint(&k, &c->rsa_e, &c->rsa_e_len) != 0) + return -1; + if (c->rsa_n_len >= 64 && c->rsa_n_len <= 512) + c->key_type = X509_KEY_RSA; + } + else if (OID_EQ(oid, oid_len, oid_ec_pub)) + { + const uint8_t *curve; + size_t curve_len; + if (der_read(¶ms, DER_OID, &curve, &curve_len) != 0) + return -1; + if (OID_EQ(curve, curve_len, oid_p256) && vl == 65 && v[0] == 0x04) + { + c->ec_point = v; + c->key_type = X509_KEY_P256; + } + else if (OID_EQ(curve, curve_len, oid_p384) && vl == 97 && v[0] == 0x04) + { + c->ec_point = v; + c->key_type = X509_KEY_P384; + } + } + return 0; +} + +/* Finds the commonName in a Name: SEQUENCE OF SET OF SEQUENCE { OID, value }. */ +static void x509_find_cn(struct x509_cert *c) +{ + struct der name, rdn, atv; + name.p = c->subject; + name.end = c->subject + c->subject_len; + /* subject points at the whole SEQUENCE TLV; step inside it. */ + if (der_enter(&name, DER_SEQUENCE, &name) != 0) + return; + while (name.p < name.end) + { + if (der_enter(&name, DER_SET, &rdn) != 0) + return; + while (rdn.p < rdn.end) + { + const uint8_t *oid, *v; + size_t oid_len, vl; + if (der_enter(&rdn, DER_SEQUENCE, &atv) != 0) + return; + if (der_read(&atv, DER_OID, &oid, &oid_len) != 0) + return; + if (OID_EQ(oid, oid_len, oid_cn) && atv.p < atv.end) + { + uint8_t tag = *atv.p; + if ((tag == DER_UTF8 || tag == DER_PRINTABLE || tag == DER_IA5) + && der_read(&atv, tag, &v, &vl) == 0) + { + c->cn = v; + c->cn_len = vl; + } + return; + } + } + } +} + +static int x509_parse_extensions(struct x509_cert *c, struct der *d) +{ + struct der exts, ext; + if (der_enter(d, DER_SEQUENCE, &exts) != 0) + return -1; + while (exts.p < exts.end) + { + const uint8_t *oid, *v; + size_t oid_len, vl; + struct der val; + int critical = 0; + if (der_enter(&exts, DER_SEQUENCE, &ext) != 0) + return -1; + if (der_read(&ext, DER_OID, &oid, &oid_len) != 0) + return -1; + if (der_peek(&ext, 0x01)) + { + if (der_read(&ext, 0x01, &v, &vl) != 0 || vl != 1) /* critical BOOLEAN */ + return -1; + critical = v[0] != 0; + } + if (der_read(&ext, DER_OCTET, &v, &vl) != 0) + return -1; + val.p = v; + val.end = v + vl; + + /* An extension marked critical that this verifier does not + * understand makes the certificate unusable (RFC 5280 4.2): + * the ones it acts on are handled below, a few others are + * known to be safe to skip, everything else is refused. */ + if (critical + && !OID_EQ(oid, oid_len, oid_basic_constr) + && !OID_EQ(oid, oid_len, oid_key_usage) + && !OID_EQ(oid, oid_len, oid_san) + && !OID_EQ(oid, oid_len, oid_ext_key_usage)) + return -1; + + if (OID_EQ(oid, oid_len, oid_basic_constr)) + { + struct der bc; + if (der_enter(&val, DER_SEQUENCE, &bc) != 0) + return -1; + c->is_ca = 0; + if (der_peek(&bc, 0x01)) + { + const uint8_t *b; + size_t bl; + if (der_read(&bc, 0x01, &b, &bl) != 0 || bl != 1) + return -1; + c->is_ca = b[0] ? 1 : 0; + } + if (der_peek(&bc, DER_INTEGER)) + { + const uint8_t *pl; + size_t pll; + if (der_read_uint(&bc, &pl, &pll) != 0 || pll > 2) + return -1; + c->path_len = (int)(pll == 2 ? (pl[0] << 8) | pl[1] : pl[0]); + } + } + else if (OID_EQ(oid, oid_len, oid_key_usage)) + { + const uint8_t *b; + size_t bl; + if (der_read(&val, DER_BITSTRING, &b, &bl) != 0 || bl < 2) + return -1; + c->key_usage = b[1]; + } + else if (OID_EQ(oid, oid_len, oid_san)) + { + const uint8_t *s; + size_t sl; + if (der_read(&val, DER_SEQUENCE, &s, &sl) != 0) + return -1; + c->san = s; + c->san_len = sl; + } + else if (OID_EQ(oid, oid_len, oid_ext_key_usage)) + { + /* ExtKeyUsageSyntax: a sequence of OIDs; note whether + * serverAuth (or anyExtendedKeyUsage) is among them */ + struct der eku; + if (der_enter(&val, DER_SEQUENCE, &eku) != 0) + return -1; + c->eku = X509_EKU_OTHER; + while (eku.p < eku.end) + { + const uint8_t *eo; + size_t el; + if (der_read(&eku, DER_OID, &eo, &el) != 0) + return -1; + if (OID_EQ(eo, el, oid_eku_server_auth) || OID_EQ(eo, el, oid_eku_any)) + c->eku = X509_EKU_SERVER_AUTH; + } + } + else + { + /* known, not acted on: subject / authority key id, policies, + * CRL points, AIA, SCTs and any other non-critical one */ + (void)oid_subject_key_id; (void)oid_authority_key_id; (void)oid_cert_policies; + (void)oid_crl_dist; (void)oid_auth_info_access; (void)oid_ct_scts; + } + } + return 0; +} + +/* RSAPrivateKey (RFC 8017 A.1.2), two-prime, version 0. */ +static int x509_rsa_key_seq(struct rsa_private_key *key, struct der *d) +{ + struct der k; + const uint8_t *v; + size_t vl; + + if (der_enter(d, DER_SEQUENCE, &k) != 0) + return -1; + if (der_read_uint(&k, &v, &vl) != 0 || vl != 1 || v[0] != 0) + return -1; + if ( der_read_uint(&k, &key->n, &key->n_len) != 0 + || der_read_uint(&k, &key->e, &key->e_len) != 0 + || der_read_uint(&k, &key->d, &key->d_len) != 0 + || der_read_uint(&k, &key->p, &key->p_len) != 0 + || der_read_uint(&k, &key->q, &key->q_len) != 0 + || der_read_uint(&k, &key->dp, &key->dp_len) != 0 + || der_read_uint(&k, &key->dq, &key->dq_len) != 0 + || der_read_uint(&k, &key->qi, &key->qi_len) != 0) + return -1; + return k.p == k.end ? 0 : -1; +} + +int x509_rsa_private_key_parse(struct rsa_private_key *key, + const uint8_t *der, size_t len) +{ + struct der d, info, params; + const uint8_t *v, *oid; + size_t vl, oid_len; + + memset(key, 0, sizeof(*key)); + d.p = der; + d.end = der + len; + if (x509_rsa_key_seq(key, &d) == 0) + return d.p == d.end ? 0 : -1; + + /* PrivateKeyInfo (RFC 5208): version 0, rsaEncryption, the + * RSAPrivateKey in an OCTET STRING, optional attributes. */ + memset(key, 0, sizeof(*key)); + d.p = der; + if (der_enter(&d, DER_SEQUENCE, &info) != 0 || d.p != d.end) + return -1; + if (der_read_uint(&info, &v, &vl) != 0 || vl != 1 || v[0] != 0) + return -1; + if (der_read_alg(&info, &oid, &oid_len, ¶ms) != 0 + || !OID_EQ(oid, oid_len, oid_rsa_enc)) + return -1; + if (der_read(&info, DER_OCTET, &v, &vl) != 0) + return -1; + d.p = v; + d.end = v + vl; + if (x509_rsa_key_seq(key, &d) != 0 || d.p != d.end) + { + memset(key, 0, sizeof(*key)); + return -1; + } + return 0; +} + +int x509_parse(struct x509_cert *c, const uint8_t *der, size_t len) +{ + struct der d, cert, tbs, params; + const uint8_t *v, *oid, *sig_oid; + size_t vl, oid_len, sig_oid_len; + const uint8_t *tbs_start; + + memset(c, 0, sizeof(*c)); + c->is_ca = -1; + c->path_len = -1; + c->key_usage = -1; + c->eku = X509_EKU_ABSENT; + c->der = der; + c->der_len = len; + + d.p = der; + d.end = der + len; + if (der_enter(&d, DER_SEQUENCE, &cert) != 0 || d.p != d.end) + return -1; + + tbs_start = cert.p; + if (der_enter(&cert, DER_SEQUENCE, &tbs) != 0) + return -1; + c->tbs = tbs_start; + c->tbs_len = (size_t)(cert.p - tbs_start); + + /* version [0] EXPLICIT INTEGER, default v1 */ + if (der_peek(&tbs, DER_CTX(0))) + { + struct der ver; + if (der_enter(&tbs, DER_CTX(0), &ver) != 0 + || der_read(&ver, DER_INTEGER, &v, &vl) != 0) + return -1; + } + if (der_read(&tbs, DER_INTEGER, &v, &vl) != 0) /* serial */ + return -1; + if (der_read_alg(&tbs, &oid, &oid_len, ¶ms) != 0) + return -1; + + c->issuer = tbs.p; + if (der_read(&tbs, DER_SEQUENCE, &v, &vl) != 0) + return -1; + c->issuer_len = (size_t)(tbs.p - c->issuer); + + { + struct der validity; + if (der_enter(&tbs, DER_SEQUENCE, &validity) != 0) + return -1; + if (der_read_time(&validity, &c->not_before) != 0 + || der_read_time(&validity, &c->not_after) != 0) + return -1; + } + + c->subject = tbs.p; + if (der_read(&tbs, DER_SEQUENCE, &v, &vl) != 0) + return -1; + c->subject_len = (size_t)(tbs.p - c->subject); + x509_find_cn(c); + + if (x509_parse_spki(c, &tbs) != 0) + return -1; + + /* issuerUniqueID [1], subjectUniqueID [2], extensions [3] */ + if (der_peek(&tbs, 0x81) && der_read(&tbs, 0x81, &v, &vl) != 0) + return -1; + if (der_peek(&tbs, 0x82) && der_read(&tbs, 0x82, &v, &vl) != 0) + return -1; + if (der_peek(&tbs, DER_CTX(3))) + { + struct der e; + if (der_enter(&tbs, DER_CTX(3), &e) != 0 + || x509_parse_extensions(c, &e) != 0) + return -1; + } + + /* signatureAlgorithm must repeat the one inside the TBS */ + if (der_read_alg(&cert, &sig_oid, &sig_oid_len, ¶ms) != 0) + return -1; + if (sig_oid_len != oid_len || memcmp(sig_oid, oid, oid_len) != 0) + return -1; + c->sig_alg = x509_sig_alg_from_oid(sig_oid, sig_oid_len); + + if (der_read(&cert, DER_BITSTRING, &v, &vl) != 0 || vl < 2 || v[0] != 0) + return -1; + c->sig = v + 1; + c->sig_len = vl - 1; + return cert.p == cert.end ? 0 : -1; +} + +/* ---- signatures --------------------------------------------------- */ + +static size_t x509_digest(enum x509_sig_alg alg, + const uint8_t *data, size_t len, uint8_t *out) +{ + switch (alg) + { + case X509_SIG_RSA_SHA256: + case X509_SIG_ECDSA_SHA256: + { + struct sha256_state s; + sha256_stream_init(&s, 0); + sha256_stream_update(&s, data, len); + sha256_stream_final(&s, out); + return 32; + } + case X509_SIG_RSA_SHA384: + case X509_SIG_ECDSA_SHA384: + { + struct sha512_state s; + sha512_stream_init(&s, 1); + sha512_stream_update(&s, data, len); + sha512_stream_final(&s, out); + return 48; + } + case X509_SIG_RSA_SHA512: + case X509_SIG_ECDSA_SHA512: + { + struct sha512_state s; + sha512_stream_init(&s, 0); + sha512_stream_update(&s, data, len); + sha512_stream_final(&s, out); + return 64; + } + default: + break; + } + return 0; +} + +int x509_verify_ecdsa_digest(const struct x509_cert *key, + const uint8_t *digest, size_t digest_len, + const uint8_t *sig, size_t sig_len) +{ + /* ECDSA-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER } */ + struct der d, sv; + const uint8_t *r, *s; + size_t rl, sl, n; + uint8_t rb[48], sb[48]; + if (key->key_type == X509_KEY_P256) + n = 32; + else if (key->key_type == X509_KEY_P384) + n = 48; + else + return -1; + d.p = sig; + d.end = sig + sig_len; + if (der_enter(&d, DER_SEQUENCE, &sv) != 0 + || der_read_uint(&sv, &r, &rl) != 0 + || der_read_uint(&sv, &s, &sl) != 0 + || rl > n || sl > n || sv.p != sv.end) + return -1; + memset(rb, 0, n); + memset(sb, 0, n); + memcpy(rb + n - rl, r, rl); + memcpy(sb + n - sl, s, sl); + if (n == 32) + return p256_ecdsa_verify(key->ec_point, digest, digest_len, rb, sb); + return p384_ecdsa_verify(key->ec_point, digest, digest_len, rb, sb); +} + +int x509_verify_signature(const struct x509_cert *c, + const struct x509_cert *issuer) +{ + uint8_t digest[64]; + size_t dlen = x509_digest(c->sig_alg, c->tbs, c->tbs_len, digest); + + if (!dlen) + return -1; + + switch (c->sig_alg) + { + case X509_SIG_RSA_SHA256: + case X509_SIG_RSA_SHA384: + case X509_SIG_RSA_SHA512: + { + enum rsa_hash h = (dlen == 32) ? RSA_HASH_SHA256 + : (dlen == 48) ? RSA_HASH_SHA384 : RSA_HASH_SHA512; + if (issuer->key_type != X509_KEY_RSA) + return -1; + return rsa_pkcs1_verify(issuer->rsa_n, issuer->rsa_n_len, + issuer->rsa_e, issuer->rsa_e_len, h, digest, dlen, + c->sig, c->sig_len); + } + case X509_SIG_ECDSA_SHA256: + case X509_SIG_ECDSA_SHA384: + case X509_SIG_ECDSA_SHA512: + return x509_verify_ecdsa_digest(issuer, digest, dlen, c->sig, c->sig_len); + default: + break; + } + return -1; +} + +/* ---- hostname ----------------------------------------------------- */ + +static int x509_name_match(const uint8_t *pat, size_t pl, const char *host) +{ + size_t hl = strlen(host); + size_t i; + + if (pl >= 2 && pat[0] == '*' && pat[1] == '.') + { + /* wildcard: the whole leftmost label, at least one more label */ + const char *dot = strchr(host, '.'); + if (!dot || dot == host) + return -1; + pat += 1; + pl -= 1; + host = dot; + hl = strlen(host); + if (!strchr(host + 1, '.')) + return -1; /* *.com */ + } + if (pl != hl) + return -1; + for (i = 0; i < pl; i++) + { + int a = pat[i], b = (unsigned char)host[i]; + if (a >= 'A' && a <= 'Z') a += 32; + if (b >= 'A' && b <= 'Z') b += 32; + if (a != b) + return -1; + } + return 0; +} + +static int x509_hexval(int c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +/* Dotted IPv4 at @s up to @end into 4 bytes. */ +static int x509_parse_ipv4(const char *s, const char *end, uint8_t *out) +{ + int i; + for (i = 0; i < 4; i++) + { + unsigned v = 0; + int digits = 0; + while (s < end && *s >= '0' && *s <= '9') + { + if (digits && v == 0) + return -1; /* no leading zeros: not octal, not ambiguous */ + v = v * 10 + (unsigned)(*s++ - '0'); + if (++digits > 3 || v > 255) + return -1; + } + if (!digits) + return -1; + out[i] = (uint8_t)v; + if (i < 3) + { + if (s >= end || *s != '.') + return -1; + s++; + } + } + return s == end ? 0 : -1; +} + +int x509_parse_ip(const char *host, uint8_t out[16]) +{ + const char *end; + uint8_t a[16]; + int n = 0, gap = -1; + const char *s; + + if (!host || !*host) + return 0; + if (!strchr(host, ':')) + return x509_parse_ipv4(host, host + strlen(host), out) == 0 ? 4 : 0; + + /* IPv6, with or without brackets; a zone ("%eth0") is not part of + * the address. */ + s = host + (*host == '['); + end = s + strcspn(s, "]%"); + if (*host == '[' && !strchr(s, ']')) + return 0; + if (end - s >= 2 && s[0] == ':' && s[1] == ':') + { + gap = 0; + s += 2; + } + else if (*s == ':') + return 0; + while (s < end) + { + unsigned v = 0; + int digits = 0; + const char *start = s; + while (s < end && x509_hexval(*s) >= 0) + { + v = (v << 4) | (unsigned)x509_hexval(*s++); + if (++digits > 4) + return 0; + } + if (s < end && *s == '.') + { + /* trailing dotted IPv4: the last 32 bits */ + if (n > 12 || x509_parse_ipv4(start, end, a + n) != 0) + return 0; + n += 4; + s = end; + break; + } + if (!digits || n > 14) + return 0; + a[n++] = (uint8_t)(v >> 8); + a[n++] = (uint8_t)v; + if (s == end) + break; + if (*s++ != ':') + return 0; + if (s < end && *s == ':') + { + if (gap >= 0) + return 0; + gap = n; + s++; + } + else if (s == end) + return 0; /* trailing single colon */ + } + if (gap >= 0) + { + int fill = 16 - n; + if (fill < 2) + return 0; + memmove(a + gap + fill, a + gap, (size_t)(n - gap)); + memset(a + gap, 0, (size_t)fill); + n = 16; + } + if (n != 16) + return 0; + memcpy(out, a, 16); + return 16; +} + +int x509_match_hostname(const struct x509_cert *c, const char *host) +{ + uint8_t ip[16]; + int iplen; + + if (!host || !*host) + return -1; + + /* An IP address matches only an iPAddress entry, byte for byte; never + * a dNSName or the CN, which would let a name certificate whose CN + * happens to read "10.0.0.1" vouch for that address (RFC 6125 6.2.1, + * RFC 9525 6.3). */ + if ((iplen = x509_parse_ip(host, ip)) != 0) + { + struct der san; + if (!c->san) + return -1; + san.p = c->san; + san.end = c->san + c->san_len; + while (san.p < san.end) + { + const uint8_t *v; + size_t vl; + uint8_t tag = *san.p; + if (der_read(&san, tag, &v, &vl) != 0) + return -1; + if (tag == 0x87 && vl == (size_t)iplen && !memcmp(v, ip, vl)) + return 0; + } + return -1; + } + + if (c->san) + { + struct der san; + san.p = c->san; + san.end = c->san + c->san_len; + while (san.p < san.end) + { + const uint8_t *v; + size_t vl; + uint8_t tag = *san.p; + if (der_read(&san, tag, &v, &vl) != 0) + return -1; + if (tag == 0x82 && x509_name_match(v, vl, host) == 0) /* dNSName */ + return 0; + } + return -1; + } + if (c->cn && x509_name_match(c->cn, c->cn_len, host) == 0) + return 0; + return -1; +} + +/* ---- trust store -------------------------------------------------- */ + +struct x509_anchor +{ + const uint8_t *der; + size_t der_len; + struct x509_cert cert; +}; + +#include +#include + +/* The anchors loaded from one source. A store is never changed once + * published: a reload builds a new one and swaps it in, and the old one + * is freed by whoever drops the last reference to it, so a chain walk + * runs over its own reference with no lock held. */ +struct x509_store +{ + mpsc_stack_node_t node; /* lock-free: on the retire list */ + struct x509_anchor *anchors; + unsigned count; + unsigned cap; + const char *src; /* the source it was loaded from */ + size_t src_len; + unsigned refs; /* under the lock: the published slot's + walks' */ + unsigned owns_der; /* PEM: decoded copies; DER: read in place */ +}; + +/* The store is process-wide and TLS handshakes run on several threads + * (updater, achievements, cloud sync), the first of them building it. + * A store is built with nothing held. */ +#if defined(HAVE_THREADS) && defined(RETRO_RETIRE_LOCK_FREE) \ + && !defined(X509_STORE_TEST_FLAG) +/* Without a lock: a walk reads the published store between entering + * and leaving, and a replaced store is freed once no walk is in + * (retro_retire.h). */ +#define X509_STORE_LOCK_FREE 1 +static retro_atomic_ptr_t x509_store_slot; +static retro_retire_t x509_store_retired; + +static void x509_store_free(struct x509_store *st); + +static void x509_store_free_node(mpsc_stack_node_t *node) +{ + x509_store_free((struct x509_store*)node); +} + +/* The published store, valid until x509_store_leave(). */ +static struct x509_store *x509_store_enter(void) +{ + struct x509_store *st; + retro_retire_enter(&x509_store_retired); + st = (struct x509_store*)retro_atomic_load_acquire_ptr(&x509_store_slot); +#ifdef X509_STORE_TEST_HOLD + X509_STORE_TEST_HOLD(); +#endif + return st; +} + +static void x509_store_leave(void) +{ + retro_retire_leave(&x509_store_retired, x509_store_free_node); +} + +/* @old was replaced: it goes once no walk is in. */ +static void x509_store_retire(struct x509_store *old) +{ + retro_retire(&x509_store_retired, old ? &old->node : NULL, + x509_store_free_node); +} +#else +static struct x509_store *x509_store_cur = NULL; +#endif + +/* Elsewhere a flag guards only swapping a store in and taking or + * dropping a reference, a few loads and stores, so a reload never + * frees a store a walk is still reading and there is no lock object to + * create on first use. */ +#if defined(HAVE_THREADS) && !defined(X509_STORE_LOCK_FREE) +#include +static retro_atomic_int_t x509_store_busy; +static void x509_store_lock(void) +{ + while (!retro_atomic_cas_int(&x509_store_busy, 0, 1)) + sthread_yield(); +} +#define X509_LOCK() x509_store_lock() +#define X509_UNLOCK() retro_atomic_store_release_int(&x509_store_busy, 0) +#elif !defined(X509_STORE_LOCK_FREE) +#define X509_LOCK() do { } while (0) +#define X509_UNLOCK() do { } while (0) +#endif + +static void x509_store_free(struct x509_store *st) +{ + unsigned i; + if (!st) + return; + if (st->owns_der) + for (i = 0; i < st->count; i++) + free((uint8_t*)st->anchors[i].der); + free(st->anchors); + free(st); +} + +#ifndef X509_STORE_LOCK_FREE +/* Under the lock. Nonzero when that was the last reference. */ +static int x509_store_unref_locked(struct x509_store *st) +{ + return st && --st->refs == 0; +} + +/* Under the lock: @st (NULL: none) replaces the published store. The + * old one comes back to be freed outside the lock, or NULL while a walk + * still holds it. */ +static struct x509_store *x509_store_publish_locked(struct x509_store *st) +{ + struct x509_store *old = x509_store_cur; + if (st) + st->refs = 1; + x509_store_cur = st; + return x509_store_unref_locked(old) ? old : NULL; +} +#endif + +void x509_trust_free(void) +{ +#ifdef X509_STORE_LOCK_FREE + x509_store_retire((struct x509_store*) + retro_atomic_exchange_ptr(&x509_store_slot, NULL)); +#else + struct x509_store *old; + X509_LOCK(); + old = x509_store_publish_locked(NULL); + X509_UNLOCK(); + x509_store_free(old); +#endif +} + +/* @der appended as an anchor: 1 when taken, 0 when it is unparseable + * or has a key this code cannot use (P-384 roots), -1 out of memory. */ +static int x509_store_push(struct x509_store *st, const uint8_t *der, + size_t der_len) +{ + struct x509_anchor *a; + if (st->count == st->cap) + { + struct x509_anchor *grown; + unsigned cap = st->cap ? st->cap * 2 : 64; + grown = (struct x509_anchor*)realloc(st->anchors, cap * sizeof(*grown)); + if (!grown) + return -1; + st->anchors = grown; + st->cap = cap; + } + a = &st->anchors[st->count]; + a->der = der; + a->der_len = der_len; + if (x509_parse(&a->cert, der, der_len) != 0 + || a->cert.key_type == X509_KEY_NONE) + return 0; + st->count++; + return 1; +} + +/* Every certificate in @pem added to @st's anchors. */ +static int x509_store_append_pem(struct x509_store *st, const char *pem, size_t len) +{ + static const char begin[] = "-----BEGIN CERTIFICATE-----"; + static const char end_[] = "-----END CERTIFICATE-----"; + const char *p = pem; + const char *e = pem + len; + + for (;;) + { + const char *b, *stop, *q; + char *clean; + size_t n; + int der_len = 0; + int r; + uint8_t *der; + + b = NULL; + for (q = p; q + sizeof(begin) - 1 <= e; q++) + if (memcmp(q, begin, sizeof(begin) - 1) == 0) + { + b = q + sizeof(begin) - 1; + break; + } + if (!b) + break; + stop = NULL; + for (q = b; q + sizeof(end_) - 1 <= e; q++) + if (memcmp(q, end_, sizeof(end_) - 1) == 0) + { + stop = q; + break; + } + if (!stop) + break; + p = stop + sizeof(end_) - 1; + + /* base64 body without the line breaks */ + if (!(clean = (char*)malloc((size_t)(stop - b) + 1))) + return -1; + n = 0; + for (q = b; q < stop; q++) + if (*q != '\n' && *q != '\r' && *q != ' ' && *q != '\t') + clean[n++] = *q; + clean[n] = '\0'; + der = unbase64(clean, (int)n, &der_len); + free(clean); + if (!der || der_len <= 0) + { + free(der); + continue; + } + + r = x509_store_push(st, der, (size_t)der_len); + if (r <= 0) + free(der); + if (r < 0) + return -1; + } + return (int)st->count; +} + +/* Every certificate in @blob, a run of DER SEQUENCEs, added to @st's + * anchors in place. */ +static int x509_store_append_der(struct x509_store *st, + const uint8_t *blob, size_t len) +{ + struct der d; + d.p = blob; + d.end = blob + len; + while (d.p < d.end) + { + const uint8_t *start = d.p; + const uint8_t *v; + size_t vl; + if (der_read(&d, DER_SEQUENCE, &v, &vl) != 0) + return -1; + if (x509_store_push(st, start, (size_t)(d.p - start)) < 0) + return -1; + } + return (int)st->count; +} + +/* The anchors in @parts, built into a new published store unless the + * one published was loaded from the same source already - the path + * every handshake after the first takes. Two first uses at once both + * build; the second to finish keeps the first's. A single PEM is a + * source of one part; @src and @src_len name the source. */ +static int x509_store_is(const struct x509_store *st, const char *src, + size_t src_len) +{ + return st && st->src == src && st->src_len == src_len && st->anchors; +} + +static int x509_trust_load(const char *const *parts, const size_t *lens, + unsigned count, const char *src, size_t src_len, int pem) +{ + struct x509_store *st, *old; + unsigned i; + int n = 0; + +#ifdef X509_STORE_LOCK_FREE + old = x509_store_enter(); + if (x509_store_is(old, src, src_len)) + n = (int)old->count; + x509_store_leave(); +#else + X509_LOCK(); + if (x509_store_is(x509_store_cur, src, src_len)) + n = (int)x509_store_cur->count; + X509_UNLOCK(); +#endif + if (n) + return n; + + if (!(st = (struct x509_store*)calloc(1, sizeof(*st)))) + n = -1; + else + st->owns_der = pem ? 1 : 0; + for (i = 0; i < count && n >= 0; i++) + n = pem ? x509_store_append_pem(st, parts[i], lens[i]) + : x509_store_append_der(st, (const uint8_t*)parts[i], lens[i]); + +#ifdef X509_STORE_LOCK_FREE + if (n >= 0) + { + st->src = src; + st->src_len = src_len; + } + for (;;) + { + struct x509_store *cur = x509_store_enter(); + if (n >= 0 && x509_store_is(cur, src, src_len)) + { + n = (int)cur->count; /* another caller's build won; ours goes */ + x509_store_leave(); + break; + } + x509_store_leave(); + /* the store we looked at, if still published, is ours to replace */ + if (retro_atomic_cas_ptr(&x509_store_slot, cur, n >= 0 ? st : NULL)) + { + if (n >= 0) + st = NULL; + x509_store_retire(cur); + break; + } + } +#else + X509_LOCK(); + if (n >= 0 && x509_store_is(x509_store_cur, src, src_len)) + { + n = (int)x509_store_cur->count; + old = NULL; /* another caller's build won; ours goes */ + } + else if (n >= 0) + { + st->src = src; + st->src_len = src_len; + old = x509_store_publish_locked(st); + st = NULL; + } + else + old = x509_store_publish_locked(NULL); + X509_UNLOCK(); + x509_store_free(old); +#endif + x509_store_free(st); + return n; +} + +int x509_trust_load_pem(const char *pem, size_t len) +{ + return x509_trust_load(&pem, &len, 1, pem, len, 1); +} + +int x509_trust_load_pem_parts(const char *const *parts, const size_t *lens, + unsigned count) +{ + /* the part table is the source: loaded once, as a single one is */ + return x509_trust_load(parts, lens, count, (const char*)parts, count, 1); +} + +int x509_trust_load_der_parts(const unsigned char *const *parts, + const size_t *lens, unsigned count) +{ + return x509_trust_load((const char *const*)parts, lens, count, + (const char*)parts, count, 0); +} + +/* ---- chain -------------------------------------------------------- */ + +static void x509_info(char *info, size_t info_len, const char *msg) +{ + if (info && info_len) + strlcpy(info, msg, info_len); +} + +static int x509_name_eq(const struct x509_cert *a, const struct x509_cert *b) +{ + return a->subject_len == b->issuer_len + && memcmp(a->subject, b->issuer, a->subject_len) == 0; +} + +int x509_verify_chain(const uint8_t **ders, const size_t *lens, unsigned n, + const char *host, time_t now, char *info, size_t info_len) +{ + return x509_verify_chain_ex(ders, lens, n, host, now, NULL, NULL, 0, 0, + info, info_len); +} + +int x509_verify_chain_ex(const uint8_t **ders, const size_t *lens, unsigned n, + const char *host, time_t now, + const uint8_t *const *trust, const size_t *trust_lens, unsigned trust_count, + unsigned flags, char *info, size_t info_len) +{ + /* Parsed chain on the heap: one x509_cert is ~200 bytes and a + * server may send a dozen. */ + struct x509_cert *certs; + const struct x509_cert *cur; + struct x509_store *st = NULL; + /* the caller's anchors in place of the store's */ + struct x509_anchor *own = NULL; + const struct x509_anchor *anchors = NULL; + unsigned anchor_count = 0; +#ifdef X509_STORE_LOCK_FREE + int entered = 0; +#endif + unsigned i, depth; + unsigned used = 0; /* bitmask of chain certs already on the path */ + unsigned ku_need = (flags & X509_VERIFY_KEY_ENCIPHERMENT) + ? X509_KU_KEY_ENCIPHERMENT : X509_KU_DIGITAL_SIGNATURE; + int ret = -1; + + if (!n || n > 16) + { + x509_info(info, info_len, "no certificate"); + return -1; + } + if (!(certs = (struct x509_cert*)malloc(n * sizeof(*certs)))) + return -1; + + for (i = 0; i < n; i++) + if (x509_parse(&certs[i], ders[i], lens[i]) != 0) + { + x509_info(info, info_len, "malformed certificate"); + goto done; + } + + if (host && x509_match_hostname(&certs[0], host) != 0) + { + x509_info(info, info_len, "hostname mismatch"); + goto done; + } + /* Leaf policy: an extendedKeyUsage without serverAuth (a client + * or code-signing certificate) does not authenticate a server; a + * keyUsage without digitalSignature cannot sign the handshake, + * which is the only way an ECDHE suite uses the key. A CA + * certificate is not a server certificate either. */ + if (certs[0].eku == X509_EKU_OTHER) + { + x509_info(info, info_len, "certificate not for server authentication"); + goto done; + } + if (certs[0].key_usage >= 0 && !(certs[0].key_usage & ku_need)) + { + x509_info(info, info_len, (flags & X509_VERIFY_KEY_ENCIPHERMENT) + ? "certificate key cannot encipher" : "certificate key cannot sign"); + goto done; + } + if (certs[0].is_ca == 1 && n > 1) + { + x509_info(info, info_len, "server certificate is a CA"); + goto done; + } + + if (trust) + { + if (trust_count && !(own = (struct x509_anchor*)malloc( + trust_count * sizeof(*own)))) + goto done; + for (i = 0; i < trust_count; i++) + { + own[anchor_count].der = trust[i]; + own[anchor_count].der_len = trust_lens[i]; + if (x509_parse(&own[anchor_count].cert, trust[i], trust_lens[i]) == 0) + anchor_count++; + } + anchors = own; + } + else + { + /* The walk reads the store through its own reference: a reload + * meanwhile swaps in a new one and leaves this one to the walk. */ +#ifdef X509_STORE_LOCK_FREE + st = x509_store_enter(); + entered = 1; +#else + X509_LOCK(); +#ifdef X509_STORE_TEST_HOLD + X509_STORE_TEST_HOLD(); +#endif + if ((st = x509_store_cur)) + st->refs++; + X509_UNLOCK(); +#endif + if (st) + { + anchors = st->anchors; + anchor_count = st->count; + } + } + + cur = &certs[0]; + used = 1; + for (depth = 0; depth < 16; depth++) + { + const struct x509_cert *issuer = NULL; + unsigned j; + + if (now < cur->not_before || now > cur->not_after) + { + x509_info(info, info_len, "certificate expired or not yet valid"); + goto done; + } + if (depth > 0) + { + if (cur->is_ca != 1) + { + x509_info(info, info_len, "intermediate is not a CA"); + goto done; + } + if (cur->key_usage >= 0 && !(cur->key_usage & X509_KU_KEY_CERT_SIGN)) + { + x509_info(info, info_len, "intermediate cannot sign certificates"); + goto done; + } + if (cur->path_len >= 0 && (int)(depth - 1) > cur->path_len) + { + x509_info(info, info_len, "path length constraint violated"); + goto done; + } + } + + /* A trust anchor issuing this one ends the walk. */ + for (j = 0; j < anchor_count; j++) + { + const struct x509_cert *a = &anchors[j].cert; + if (x509_name_eq(a, cur) && x509_verify_signature(cur, a) == 0) + { + ret = 0; + goto done; + } + } + + /* Otherwise the next certificate in the chain that names itself + * as issuer and whose signature checks out. */ + for (j = 1; j < n; j++) + { + if (used & (1u << j)) + continue; + if (x509_name_eq(&certs[j], cur) && x509_verify_signature(cur, &certs[j]) == 0) + { + issuer = &certs[j]; + used |= 1u << j; + break; + } + } + if (!issuer) + { + x509_info(info, info_len, anchor_count + ? "issuer not found or signature invalid" + : "no trust anchors loaded"); + goto done; + } + cur = issuer; + } + x509_info(info, info_len, "chain too long"); + +done: +#ifdef X509_STORE_LOCK_FREE + if (entered) + x509_store_leave(); +#else + if (st) + { + int last; + X509_LOCK(); + last = x509_store_unref_locked(st); + X509_UNLOCK(); + if (last) + x509_store_free(st); + } +#endif + free(own); + free(certs); + return ret; +} diff --git a/dependencies/libretro-common/encodings/encoding_base64.c b/dependencies/libretro-common/encodings/encoding_base64.c new file mode 100644 index 00000000..d8d51c8b --- /dev/null +++ b/dependencies/libretro-common/encodings/encoding_base64.c @@ -0,0 +1,171 @@ +/* + https://github.com/superwills/NibbleAndAHalf + base64.h -- Fast base64 encoding and decoding. + version 1.0.0, April 17, 2013 143a + Copyright (C) 2013 William Sherif + 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. + William Sherif + will.sherif@gmail.com + YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz + + + Modified for RetroArch formatting, logging, and header files. +*/ + + +#include +#include +#include + +static const char* b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* maps A=>0,B=>1.. */ +static const unsigned char unb64[]={ + 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, 62, 0, 0, 0, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 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, 0, 0, 0, 0, 0, 0, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, +}; /* This array has 256 elements */ + +/* + Converts binary data of length=len to base64 characters. + Length of the resultant string is stored in flen + (you must pass pointer flen). +*/ +char* base64(const void* binaryData, int len, int *flen) +{ + char* res; + int byteNo; /* I need this after the loop */ + const unsigned char* bin = (const unsigned char*) binaryData; + int rc = 0; /* result counter */ + int modulusLen = len % 3 ; + /* 2 gives 1 and 1 gives 2, but 0 gives 0. */ + int pad = ((modulusLen&1)<<1) + ((modulusLen&2)>>1); + + *flen = 4*(len + pad)/3; + if (!(res = (char*) malloc(*flen + 1))) /* and one for the NULL */ + return 0; + + for (byteNo=0; byteNo <= len-3; byteNo+=3) + { + unsigned char BYTE0 = bin[byteNo]; + unsigned char BYTE1 = bin[byteNo+1]; + unsigned char BYTE2 = bin[byteNo+2]; + + res[rc++] = b64[BYTE0 >> 2]; + res[rc++] = b64[((0x3&BYTE0)<<4) + (BYTE1 >> 4)]; + res[rc++] = b64[((0x0f&BYTE1)<<2) + (BYTE2>>6)]; + res[rc++] = b64[0x3f&BYTE2]; + } + + if (pad==2) + { + res[rc++] = b64[bin[byteNo] >> 2]; + res[rc++] = b64[(0x3&bin[byteNo])<<4]; + res[rc++] = '='; + res[rc++] = '='; + } + else if (pad==1) + { + res[rc++] = b64[bin[byteNo] >> 2]; + res[rc++] = b64[((0x3&bin[byteNo])<<4) + (bin[byteNo+1] >> 4)]; + res[rc++] = b64[(0x0f&bin[byteNo+1])<<2]; + res[rc++] = '='; + } + + res[rc]=0; /* NULL TERMINATOR! ;) */ + return res; +} + +unsigned char* unbase64(const char* ascii, int len, int *flen) +{ + int charNo; + unsigned char *bin; + const unsigned char *safeAsciiPtr = (const unsigned char*) ascii; + int cb = 0; + int pad = 0; + + /* Valid base64 has length that is a non-zero multiple of 4. + * Shorter or misaligned inputs are malformed and have historically + * caused 1-byte heap overflows when pad > 0 (e.g. "AB="). */ + if (len < 4 || (len & 3) != 0) + { + *flen = 0; + return 0; + } + + if (safeAsciiPtr[len-1]=='=') + ++pad; + if (safeAsciiPtr[len-2]=='=') + ++pad; + + *flen = 3*len/4 - pad; + if (!(bin = (unsigned char*)malloc(*flen))) + return 0; + + for (charNo=0; charNo <= len-4-pad; charNo+=4) + { + int A = unb64[safeAsciiPtr[charNo]]; + int B = unb64[safeAsciiPtr[charNo+1]]; + int C = unb64[safeAsciiPtr[charNo+2]]; + int D = unb64[safeAsciiPtr[charNo+3]]; + + bin[cb++] = (A<<2) | (B>>4); + bin[cb++] = (B<<4) | (C>>2); + bin[cb++] = (C<<6) | (D); + } + + if (pad==1) + { + int A = unb64[safeAsciiPtr[charNo]]; + int B = unb64[safeAsciiPtr[charNo+1]]; + int C = unb64[safeAsciiPtr[charNo+2]]; + + bin[cb++] = (A<<2) | (B>>4); + bin[cb++] = (B<<4) | (C>>2); + } + else if (pad==2) + { + int A = unb64[safeAsciiPtr[charNo]]; + int B = unb64[safeAsciiPtr[charNo+1]]; + + bin[cb++] = (A<<2) | (B>>4); + } + + return bin; +} + diff --git a/dependencies/libretro-common/encodings/encoding_utf.c b/dependencies/libretro-common/encodings/encoding_utf.c new file mode 100644 index 00000000..8ff36032 --- /dev/null +++ b/dependencies/libretro-common/encodings/encoding_utf.c @@ -0,0 +1,1020 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (encoding_utf.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#if defined(_WIN32) && !defined(_XBOX) +#include +#elif defined(_XBOX) +#include +#endif + +#define UTF8_WALKBYTE(string) (*((*(string))++)) + +/* Number of leading 1-bits of a byte, which for a lead byte is the + * length in bytes of the sequence it introduces: 0 for ASCII, 1 for a + * continuation byte, 2..4 for the valid leads, 5..7 for invalid ones. + * Replaces a leading_ones() bit-counting loop. + * + * Used by the decoders, which need the exact value. The scanners that + * only need a length deliberately do not use it: indexing this table + * puts a second dependent load in their pointer advance, which costs + * more than the branch it removes. */ +static const uint8_t utf8_lut[256] = { + /* 0x00..0x7F: 0 leading ones (ASCII) */ + 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,0,0,0,0,0,0,0,0,0, + /* 0x80..0xBF: 1 leading one (continuation byte) */ + 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,1,1,1, + /* 0xC0..0xDF: 2 leading ones (2-byte sequence) */ + 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, + /* 0xE0..0xEF: 3 leading ones (3-byte sequence) */ + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + /* 0xF0..0xF7: 4 leading ones (4-byte sequence) */ + 4,4,4,4,4,4,4,4, + /* 0xF8..0xFB: 5 leading ones */ + 5,5,5,5, + /* 0xFC..0xFD: 6 leading ones */ + 6,6, + /* 0xFE..0xFF: 7+ leading ones (invalid) */ + 7,7 +}; + +/** + * utf8_conv_utf32: + * + * Simple implementation. Assumes the sequence is + * properly synchronized and terminated. + * + * Optimized: replaced leading_ones() loop with LUT, + * fast-path for ASCII, and unrolled continuation-byte reads. + **/ +size_t utf8_conv_utf32(uint32_t *out, size_t out_chars, + const char *in, size_t in_size) +{ + size_t ret = 0; + while (in_size && out_chars) + { + uint32_t c; + uint8_t first; + unsigned ones; + + /* Fast path: batch ASCII characters. + * + * Same word test as the utf8len counting loop: a byte is ASCII + * iff bit 7 is clear, so one masked 64-bit compare clears eight + * bytes at a time. The loads go through memcpy, so alignment + * does not matter and no strict-aliasing rule is broken. Widening + * to uint32_t is done per byte, which is endian neutral. + * + * The word loop is only entered when the next byte is ASCII, so + * multibyte-dense text does not pay a wide load and test on + * every character. */ + if ((uint8_t)*in < 0x80) + { + while (in_size >= 8 && out_chars >= 8) + { + uint64_t w; + memcpy(&w, in, sizeof(w)); + if (w & 0x8080808080808080ULL) + break; + out[0] = (uint8_t)in[0]; + out[1] = (uint8_t)in[1]; + out[2] = (uint8_t)in[2]; + out[3] = (uint8_t)in[3]; + out[4] = (uint8_t)in[4]; + out[5] = (uint8_t)in[5]; + out[6] = (uint8_t)in[6]; + out[7] = (uint8_t)in[7]; + in += 8; + out += 8; + in_size -= 8; + out_chars -= 8; + ret += 8; + } + + while (in_size && out_chars && (uint8_t)*in < 0x80) + { + *out++ = (uint8_t)*in++; + in_size--; + out_chars--; + ret++; + } + + if (!in_size || !out_chars) + break; + } + + first = (uint8_t)*in++; + + /* Dispatch on the lead byte with direct comparisons instead of + * the LUT: the table indexing puts a dependent load on the + * critical path of every multibyte character, and the compare + * chain resolves 2- and 3-byte leads (the common cases) first. */ + if (first < 0xE0) + { + if (first < 0xC0) /* Continuation byte: desync. */ + break; + if (in_size < 2) /* Not enough data. */ + break; + c = ((uint32_t)(first & 0x1F) << 6) + | ((uint8_t)*in++ & 0x3F); + in_size -= 2; + } + else if (first < 0xF0) + { + if (in_size < 3) + break; + c = (uint32_t)(first & 0x0F) << 6; + c = (c | ((uint8_t)*in++ & 0x3F)) << 6; + c = c | ((uint8_t)*in++ & 0x3F); + in_size -= 3; + } + else if (first < 0xF8) + { + if (in_size < 4) + break; + c = (uint32_t)(first & 0x07) << 6; + c = (c | ((uint8_t)*in++ & 0x3F)) << 6; + c = (c | ((uint8_t)*in++ & 0x3F)) << 6; + c = c | ((uint8_t)*in++ & 0x3F); + in_size -= 4; + } + else + { + /* 5/6-byte forms and 0xFE/0xFF: invalid UTF-8. Decode the + * 5/6-byte shapes as before (garbage in, garbage out), stop + * on 0xFE/0xFF. */ + unsigned i; + ones = utf8_lut[first]; + if (ones > 6) + break; + if (ones > in_size) + break; + c = first & ((1 << (7 - ones)) - 1); + for (i = 0; i < ones - 1; i++) + c = (c << 6) | ((uint8_t)*in++ & 0x3F); + in_size -= ones; + } + + *out++ = c; + out_chars--; + ret++; + } + return ret; +} + +/** + * utf16_conv_utf8: + * + * Leaf function. + * + * Optimized: separated counting-only path (out==NULL) from + * encoding path to eliminate per-byte branch on `out`. + * Added explicit fast-path for BMP 2-byte and 3-byte encodings. + **/ +bool utf16_conv_utf8(uint8_t *out, size_t *out_chars, + const uint16_t *in, size_t in_size) +{ + size_t out_pos = 0; + size_t in_pos = 0; + + if (!out) + { + /* Counting-only pass: no stores, + no per-byte `if (out)` branches */ + for (;;) + { + uint32_t value; + if (in_pos == in_size) + { + *out_chars = out_pos; + return true; + } + value = in[in_pos++]; + + if (value < 0x80) + { + out_pos++; + continue; + } + + if (value >= 0xD800 && value < 0xE000) + { + uint32_t c2; + if (value >= 0xDC00 || in_pos == in_size) + break; + c2 = in[in_pos++]; + if (c2 < 0xDC00 || c2 >= 0xE000) + break; + value = (((value - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000; + } + + if (value < 0x800) + out_pos += 2; + else if (value < 0x10000) + out_pos += 3; + else + out_pos += 4; + } + *out_chars = out_pos; + return false; + } + + /* Encoding pass */ + for (;;) + { + uint32_t value; + if (in_pos == in_size) + { + *out_chars = out_pos; + return true; + } + + /* Batch ASCII run: avoid per-char branch into multi-byte path */ + while (in_pos < in_size && in[in_pos] < 0x80) + out[out_pos++] = (uint8_t)in[in_pos++]; + + if (in_pos == in_size) + { + *out_chars = out_pos; + return true; + } + + value = in[in_pos++]; + + if (value >= 0xD800 && value < 0xE000) + { + uint32_t c2; + if (value >= 0xDC00 || in_pos == in_size) + break; + c2 = in[in_pos++]; + if (c2 < 0xDC00 || c2 >= 0xE000) + break; + value = (((value - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000; + } + + if (value < 0x800) + { + /* 2-byte sequence */ + out[out_pos] = (uint8_t)(0xC0 | (value >> 6)); + out[out_pos + 1] = (uint8_t)(0x80 | (value & 0x3F)); + out_pos += 2; + } + else if (value < 0x10000) + { + /* 3-byte sequence */ + out[out_pos] = (uint8_t)(0xE0 | (value >> 12)); + out[out_pos + 1] = (uint8_t)(0x80 | ((value >> 6) & 0x3F)); + out[out_pos + 2] = (uint8_t)(0x80 | (value & 0x3F)); + out_pos += 3; + } + else + { + /* 4-byte sequence */ + out[out_pos] = (uint8_t)(0xF0 | (value >> 18)); + out[out_pos + 1] = (uint8_t)(0x80 | ((value >> 12) & 0x3F)); + out[out_pos + 2] = (uint8_t)(0x80 | ((value >> 6) & 0x3F)); + out[out_pos + 3] = (uint8_t)(0x80 | (value & 0x3F)); + out_pos += 4; + } + } + + *out_chars = out_pos; + return false; +} + +/** + * utf8cpy: + * + * Acts mostly like strlcpy. + * + * Copies the given number of UTF-8 characters, + * but at most @len bytes. + * + * Always NULL terminates. Does not copy half a character. + * @s is assumed valid UTF-8. + * Use only if @chars is considerably less than @len. + * + * Nothing is written when @len is 0, since there is no room even + * for the terminator. + * + * @return Number of bytes. + **/ +size_t utf8cpy(char *s, size_t len, const char *in, size_t chars) +{ + size_t byte_count; + const uint8_t *sb = (const uint8_t*)in; + const uint8_t *sb_org = sb; + + if (!in || !len) + return 0; + + while ((size_t)(sb - sb_org) < len - 1 && *sb && chars-- > 0) + { + /* Stepping over continuation bytes stops at the terminator by + * itself, so a truncated sequence cannot overrun the buffer. */ + sb++; + while ((*sb & 0xC0) == 0x80) + sb++; + } + + if ((size_t)(sb - sb_org) > len - 1) + { + sb = sb_org + len - 1; + /* @in may itself begin with continuation bytes; do not scan + * backwards out of the buffer looking for a lead byte. */ + while (sb > sb_org && (*sb & 0xC0) == 0x80) + sb--; + } + + byte_count = (size_t)(sb - sb_org); + memcpy(s, sb_org, byte_count); + s[byte_count] = '\0'; + return byte_count; +} + +/** + * utf8skip: + * + * Leaf function. + * + * Optimized: comparison dispatch on the lead byte (no dependent LUT + * load on the per-character path), NUL-guarded stepping over + * multibyte sequences, and ASCII runs skipped eight characters per + * masked 64-bit word test. The word test requires every byte to be + * ASCII and non-NUL, so it can neither overshoot the terminator nor + * miscount characters. memcpy load: alignment/aliasing safe, endian + * neutral. + **/ +const char *utf8skip(const char *str, size_t chars) +{ + const uint8_t *strb = (const uint8_t*)str; + + if (!chars) + return str; + + do + { + uint8_t b = *strb; + if (!b) + break; + if (b < 0xC0) + { + /* ASCII or lone continuation byte: one char, one byte. */ + strb++; + if (b < 0x80) + { + /* Batch the rest of an ASCII run. The current character + * is consumed by the --chars below, so batch only while + * more than one character remains in the budget. + * + * Byte steps rather than a wide word test: utf8skip's + * contract is a NUL-terminated string with no length, so + * an 8-byte load could read past a terminator that falls + * inside the word - on a tightly sized allocation that is + * a read beyond the end of the buffer, even though the + * zero test would stop the cursor before consuming those + * bytes. Stepping a byte at a time stops exactly at the + * terminator or the first multibyte lead and still skips + * the per-character dispatch for the run; only the load + * width changes. */ + while (chars > 1) + { + uint8_t nb = *strb; + if (nb == 0 || nb >= 0x80) + break; + strb++; + chars--; + } + } + } + else if (b < 0xE0) + { + strb++; + if (*strb) + strb++; + } + else if (b < 0xF0) + { + strb++; + if (*strb) + { + strb++; + if (*strb) + strb++; + } + } + else if (b < 0xF8) + { + strb++; + if (*strb) + { + strb++; + if (*strb) + { + strb++; + if (*strb) + strb++; + } + } + } + else + { + /* Invalid 5/6/7-lead: step over utf8_lut[b] bytes stopping + * at NUL, exactly as the LUT loop did. */ + unsigned ones = utf8_lut[b]; + unsigned i; + for (i = 0; i < ones && strb[i]; i++) + ; + strb += i; + } + } while (--chars); + + return (const char*)strb; +} + +/** + * utf8len: + * + * Leaf function. + **/ +size_t utf8len(const char *string) +{ + const unsigned char *p; + size_t n; + size_t ret = 0; + + if (!string) + return 0; + + p = (const unsigned char*)string; + n = strlen(string); + + /* Byte at a time up to the first aligned address. */ + while (n && (((size_t)p & 7) != 0)) + { + if ((*p & 0xC0) != 0x80) + ret++; + p++; + n--; + } + + /* The length is known, so the word loop needs no terminator test + * and never reads a byte the caller did not supply. */ + while (n >= 8) + { + uint64_t w; + uint64_t c; + memcpy(&w, p, sizeof(w)); + /* A continuation byte is the pattern 10xxxxxx: bit 7 set and + * bit 6 clear. Both halves of the test stay inside their own + * byte, so this is endian neutral. */ + c = (w & ~(w << 1) & 0x8080808080808080ULL) >> 7; + /* Horizontal sum. Every byte of c is 0 or 1 and the running + * total tops out at 8, so the folds cannot carry out of a + * byte and no 64-bit multiply is needed. */ + c += c >> 32; + c += c >> 16; + c += c >> 8; + ret += 8 - (size_t)(c & 0xFF); + p += 8; + n -= 8; + } + + while (n) + { + if ((*p & 0xC0) != 0x80) + ret++; + p++; + n--; + } + return ret; +} + +/** + * utf8_walk: + * + * Does not validate the input, but never reads or steps past a + * terminating NUL, even mid-sequence: a truncated multibyte tail + * previously read up to three bytes beyond the terminator and left + * the cursor past it, walking a while (*str) caller out of the + * buffer. + * + * Leaf function. + * + * @return Returns garbage if it's not UTF-8. + **/ +uint32_t utf8_walk(const char **string) +{ + const uint8_t *s = (const uint8_t*)*string; + uint8_t first = *s++; + uint8_t b; + uint32_t ret; + + if (first < 0x80) + { + *string = (const char*)s; + return first; + } + + /* Dispatch on the lead byte with direct comparisons, matching + * utf8_conv_utf32: the LUT indexing put a dependent load on the + * critical path of every glyph decoded by the per-frame text + * renderers, and the compare chain resolves the common 2- and + * 3-byte leads first. Continuation and 5-byte-plus leads take the + * final branch and decode to garbage, as before. + * + * Each continuation read tests the byte it already loaded against + * NUL before consuming it; the branch is never taken on valid + * input, and on a truncated tail the cursor parks at the + * terminator with a partial (garbage) return. */ + if (first < 0xE0) + { + if (first < 0xC0) + /* Lone continuation byte: desync. Do not consume another + * byte, or a caller iterating with while (*str) could be + * carried past the terminator. Same masked-garbage return + * as the LUT path produced. */ + ret = first & 0x3F; + else + { + ret = first & 0x1F; + if ((b = *s) != 0) + { + s++; + ret = (ret << 6) | (b & 0x3F); + } + } + } + else if (first < 0xF0) + { + ret = first & 0x0F; + if ((b = *s) != 0) + { + s++; + ret = (ret << 6) | (b & 0x3F); + if ((b = *s) != 0) + { + s++; + ret = (ret << 6) | (b & 0x3F); + } + } + } + else if (first < 0xF8) + { + ret = first & 0x07; + if ((b = *s) != 0) + { + s++; + ret = (ret << 6) | (b & 0x3F); + if ((b = *s) != 0) + { + s++; + ret = (ret << 6) | (b & 0x3F); + if ((b = *s) != 0) + { + s++; + ret = (ret << 6) | (b & 0x3F); + } + } + } + } + else + ret = first & ((1 << (7 - utf8_lut[first])) - 1); + + *string = (const char*)s; + return ret; +} + +static bool utf16_to_char(uint8_t **utf_data, + size_t *dest_len, const uint16_t *in) +{ + const uint16_t *p = in; + /* Find length in a single scan */ + while (*p != 0) + p++; + { + size_t in_len = (size_t)(p - in); + /* Single pass with a worst-case allocation instead of the + * count-then-encode double pass: a UTF-16 unit encodes to at + * most three UTF-8 bytes (a surrogate pair is two units for + * four bytes, i.e. two bytes per unit), so 3n + 1 always fits + * and the counting pass cost half the throughput of this + * function. The buffer is short-lived - the only caller copies + * out of it and frees it immediately. */ + if (in_len > (((size_t)-1) - 1) / 3) + return false; + if ((*utf_data = (uint8_t*)malloc(3 * in_len + 1)) != 0) + return utf16_conv_utf8(*utf_data, dest_len, in, in_len); + } + return false; +} + +/** + * utf16_to_char_string: + **/ +bool utf16_to_char_string(const uint16_t *in, char *s, size_t len) +{ + size_t _len = 0; + uint8_t *utf16_data = NULL; + bool ret = utf16_to_char(&utf16_data, &_len, in); + if (ret) + { + utf16_data[_len] = 0; + strlcpy(s, (const char*)utf16_data, len); + } + free(utf16_data); + utf16_data = NULL; + return ret; +} + +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) +/** + * mb_to_mb_string_alloc: + * + * @return Returned pointer MUST be freed by the caller if non-NULL. + **/ +static char *mb_to_mb_string_alloc(const char *str, + enum CodePage cp_in, enum CodePage cp_out) +{ + wchar_t *path_buf_wide = NULL; + int path_buf_wide_len = MultiByteToWideChar(cp_in, 0, str, -1, NULL, 0); + + /* Windows 95 will return 0 from these functions with + * a UTF8 codepage set without MSLU. + * + * From an unknown MSDN version (others omit this info): + * - CP_UTF8 Windows 98/Me, Windows NT 4.0 and later: + * Translate using UTF-8. When this is set, dwFlags must be zero. + * - Windows 95: Under the Microsoft Layer for Unicode, + * MultiByteToWideChar also supports CP_UTF7 and CP_UTF8. + */ + + if (!path_buf_wide_len) + return strdup(str); + + /* +1 element for the terminator; the old expression added + * sizeof(wchar_t) ELEMENTS (a byte count used as an element + * count), harmlessly over-allocating. */ + if ((path_buf_wide = (wchar_t*) + calloc((size_t)path_buf_wide_len + 1, sizeof(wchar_t)))) + { + MultiByteToWideChar(cp_in, 0, + str, -1, path_buf_wide, path_buf_wide_len); + + if (*path_buf_wide) + { + int path_buf_len = WideCharToMultiByte(cp_out, 0, + path_buf_wide, -1, NULL, 0, NULL, NULL); + + if (path_buf_len) + { + char *path_buf = (char*) + calloc((size_t)path_buf_len + 1, sizeof(char)); + + if (path_buf) + { + WideCharToMultiByte(cp_out, 0, + path_buf_wide, -1, path_buf, + path_buf_len, NULL, NULL); + + free(path_buf_wide); + + if (*path_buf) + return path_buf; + + free(path_buf); + return NULL; + } + } + else + { + free(path_buf_wide); + return strdup(str); + } + } + + free(path_buf_wide); + } + + return NULL; +} +#endif + +/** + * utf8_to_local_string_alloc: + * + * @return Returned pointer MUST be freed by the caller if non-NULL. + **/ +char* utf8_to_local_string_alloc(const char *str) +{ + if (str && *str) +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) + return mb_to_mb_string_alloc(str, CODEPAGE_UTF8, CODEPAGE_LOCAL); +#else + return strdup(str); /* Assume string needs no modification if not on Windows */ +#endif + return NULL; +} + +/** + * local_to_utf8_string: + * + * The guard is the one in local_to_utf8_string_alloc() below: where it + * resolves to a plain copy there is nothing to convert, so there is + * nothing to allocate either. + **/ +bool local_to_utf8_string(const char *in, char *s, size_t len) +{ + if (!s || !len) + return false; + s[0] = '\0'; + if (!in || !*in) + return true; +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) + { + char *tmp = mb_to_mb_string_alloc(in, CODEPAGE_LOCAL, CODEPAGE_UTF8); + if (!tmp) + return false; + strlcpy(s, tmp, len); + free(tmp); + } +#else + strlcpy(s, in, len); +#endif + return true; +} + +/** + * local_to_utf8_string_alloc: + * + * @return Returned pointer MUST be freed by the caller if non-NULL. + **/ +char *local_to_utf8_string_alloc(const char *str) +{ + if (str && *str) +#if defined(_WIN32) && !defined(_XBOX) && !defined(UNICODE) + return mb_to_mb_string_alloc(str, CODEPAGE_LOCAL, CODEPAGE_UTF8); +#else + return strdup(str); /* Assume string needs no modification if not on Windows */ +#endif + return NULL; +} + +/** + * utf8_to_utf16_string_alloc: + * + * @return Returned pointer MUST be freed by the caller if non-NULL. + **/ +wchar_t *utf8_to_utf16_string_alloc(const char *str) +{ +#ifdef _WIN32 + int _len = 0; +#endif + wchar_t *buf = NULL; + + if (!str || !*str) + return NULL; + +#ifdef _WIN32 + if ((_len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0))) + { + if (!(buf = (wchar_t*)calloc(_len, sizeof(wchar_t)))) + return NULL; + + if ((MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, _len)) < 0) + { + free(buf); + return NULL; + } + } + else + { + /* Fallback to ANSI codepage instead */ + if ((_len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0))) + { + if (!(buf = (wchar_t*)calloc(_len, sizeof(wchar_t)))) + return NULL; + + if ((MultiByteToWideChar(CP_ACP, 0, str, -1, buf, _len)) < 0) + { + free(buf); + return NULL; + } + } + } +#else + /* Locale-independent conversion. mbstowcs only decodes UTF-8 when + * the active locale says so: under the default C/POSIX locale + * (headless machines, containers, any process that never calls + * setlocale) it fails on the first non-ASCII byte and this + * function returned NULL. Decode with the in-house converter + * instead: exact UTF-32 into 32-bit wchar_t, UTF-16 with + * surrogate pairs when wchar_t is 16-bit. Scalars above U+10FFFF + * (only reachable from invalid input) become U+FFFD on the + * 16-bit path. */ + { + size_t n8 = strlen(str); + uint32_t *u32 = (uint32_t*)malloc(n8 * sizeof(uint32_t)); + + if (!u32) + return NULL; + + { + size_t n32 = utf8_conv_utf32(u32, n8, str, n8); + size_t i; + + if (sizeof(wchar_t) == 2) + { + /* Worst case two units per scalar, plus terminator */ + if ((buf = (wchar_t*)malloc((2 * n32 + 1) * sizeof(wchar_t)))) + { + size_t o = 0; + for (i = 0; i < n32; i++) + { + uint32_t cp = u32[i]; + if (cp < 0x10000) + buf[o++] = (wchar_t)cp; + else if (cp <= 0x10FFFF) + { + cp -= 0x10000; + buf[o++] = (wchar_t)(0xD800 | (cp >> 10)); + buf[o++] = (wchar_t)(0xDC00 | (cp & 0x3FF)); + } + else + buf[o++] = (wchar_t)0xFFFD; + } + buf[o] = 0; + } + } + else + { + if ((buf = (wchar_t*)malloc((n32 + 1) * sizeof(wchar_t)))) + { + for (i = 0; i < n32; i++) + buf[i] = (wchar_t)u32[i]; + buf[n32] = 0; + } + } + } + + free(u32); + } +#endif + + return buf; +} + +/** + * utf16_to_utf8_string_alloc: + * + * @return Returned pointer MUST be freed by the caller if non-NULL. + **/ +char *utf16_to_utf8_string_alloc(const wchar_t *str) +{ +#ifdef _WIN32 + int _len = 0; +#else + size_t _len = 0; +#endif + char *buf = NULL; + + if (!str || !*str) + return NULL; + +#ifdef _WIN32 + { + UINT code_page = CP_UTF8; + + /* fallback to ANSI codepage instead */ + if (!(_len = WideCharToMultiByte(code_page, + 0, str, -1, NULL, 0, NULL, NULL))) + { + code_page = CP_ACP; + _len = WideCharToMultiByte(code_page, + 0, str, -1, NULL, 0, NULL, NULL); + } + + if (!(buf = (char*)calloc(_len, sizeof(char)))) + return NULL; + + if (WideCharToMultiByte(code_page, + 0, str, -1, buf, _len, NULL, NULL) < 0) + { + free(buf); + return NULL; + } + } +#else + /* Locale-independent conversion; see utf8_to_utf16_string_alloc. + * wcstombs had the same C/POSIX-locale failure on non-ASCII. + * 32-bit wchar_t is re-expressed as UTF-16 (exact for valid + * scalars) so the existing count-then-encode converter can do the + * encoding; unpaired surrogates or out-of-range values make it + * bail, and NULL is returned as the old code did for input + * wcstombs could not represent. */ + { + size_t in_len = 0; + const wchar_t *p = str; + uint16_t *u16; + + while (*p++) + in_len++; + + /* Worst case two units per wchar */ + if (!(u16 = (uint16_t*)malloc((2 * in_len) * sizeof(uint16_t)))) + return NULL; + + { + size_t n16 = 0; + size_t i; + bool ok = true; + + if (sizeof(wchar_t) == 2) + { + for (i = 0; i < in_len; i++) + u16[n16++] = (uint16_t)str[i]; + } + else + { + for (i = 0; i < in_len; i++) + { + uint32_t cp = (uint32_t)str[i]; + if (cp < 0x10000) + u16[n16++] = (uint16_t)cp; + else if (cp <= 0x10FFFF) + { + cp -= 0x10000; + u16[n16++] = (uint16_t)(0xD800 | (cp >> 10)); + u16[n16++] = (uint16_t)(0xDC00 | (cp & 0x3FF)); + } + else + { + ok = false; + break; + } + } + } + + if (ok && utf16_conv_utf8(NULL, &_len, u16, n16)) + { + if ((buf = (char*)malloc(_len + 1))) + { + if (utf16_conv_utf8((uint8_t*)buf, &_len, u16, n16)) + buf[_len] = '\0'; + else + { + free(buf); + buf = NULL; + } + } + } + } + + free(u16); + } +#endif + + return buf; +} diff --git a/dependencies/libretro-common/features/cpu_class.h b/dependencies/libretro-common/features/cpu_class.h new file mode 100644 index 00000000..c71b0eb5 --- /dev/null +++ b/dependencies/libretro-common/features/cpu_class.h @@ -0,0 +1,271 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (cpu_class.h). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +/* Per-processor performance class: the one source of truth shared by + * cpu_features_get_processor_order() (features_cpu.c) and + * sthread_prefer_fast_cores() / sthread_get_core_topology() + * (rthreads.c), so a thread pinned "to the fast cores" and a thread + * pinned "to the strongest processor" land on the same silicon. + * + * This is a private implementation header: every function is static + * and the file is included by exactly the two .c files above, because + * neither may link the other (rthreads.c is built on its own by the + * sample harnesses, features_cpu.c by the cores). + * + * cpu_class_read() fills cls[id] for id < len with a class where a + * higher value is a faster core, and returns the number of ids + * covered, or 0 when the platform publishes no class at all - a + * homogeneous part, or one this code cannot see into - in which case + * every processor is to be taken as the same class. + * + * Linux, in order of trust: + * 1. The kernel's own hybrid classification: on Intel hybrid parts + * the perf driver publishes the P-cores as cpu_core/cpus and the + * E-cores as cpu_atom/cpus. + * 2. cpu_capacity, the scheduler's per-CPU throughput figure on ARM + * big.LITTLE, where the biggest cluster is 1024 and a little one a + * few hundred. A three-tier part (one prime, some big, some + * little) keeps its big cores in the fast class by taking + * everything within CPU_CLASS_CAPACITY_PCT of the best. + * 3. cpuinfo_max_freq, within CPU_CLASS_FREQ_PCT of the highest. The + * band is what makes this usable on x86: favoured cores (Intel + * Turbo Boost Max 3.0, AMD CPPC preferred cores) and the two CCDs + * of an X3D part differ from their siblings by a few percent, + * while a real slow cluster sits 20-40% lower. + * Windows 10 1607+: CPU Sets EfficiencyClass, which rises with + * performance (P = 1, E = 0 on Intel hybrid). Resolved at run time so + * older targets still link and simply report nothing. + * Everything else: nothing. + * + * A builder may point CPU_CLASS_SYSFS at a fixture tree in place of + * /sys/devices/system/cpu. */ + +#ifndef __LIBRETRO_SDK_CPU_CLASS_H +#define __LIBRETRO_SDK_CPU_CLASS_H + +#include +#include +#include +#include +#include +#include + +#ifndef CPU_CLASS_SYSFS +#define CPU_CLASS_SYSFS "/sys/devices/system/cpu" +#endif +#define CPU_CLASS_CAPACITY_PCT 70 +#define CPU_CLASS_FREQ_PCT 85 +#define CPU_CLASS_MAX_IDS 256 + +#if defined(__linux__) + +/* Reads one unsigned long from a sysfs file; 0 when absent or empty. */ +static unsigned long cpu_class_sysfs_ulong(const char *path) +{ + unsigned long v = 0; + FILE *f = fopen(path, "r"); + if (!f) + return 0; + if (fscanf(f, "%lu", &v) != 1) + v = 0; + fclose(f); + return v; +} + +/* Parses a sysfs cpulist ("0-7,16-23") at path, setting mark[id] for + * each id < len it names; returns whether the file existed and named + * at least one processor. */ +static bool cpu_class_sysfs_cpulist(const char *path, + unsigned char *mark, size_t len) +{ + char line[512]; + char *p; + bool any = false; + FILE *f = fopen(path, "r"); + if (!f) + return false; + line[0] = '\0'; + if (!fgets(line, sizeof(line), f)) + line[0] = '\0'; + fclose(f); + for (p = line; *p; ) + { + char *next; + unsigned long lo, hi, i; + if (*p < '0' || *p > '9') + { + p++; + continue; + } + lo = hi = strtoul(p, &next, 10); + if (*next == '-') + hi = strtoul(next + 1, &next, 10); + for (i = lo; i <= hi && i < len; i++) + { + mark[i] = 1; + any = true; + } + p = next; + } + return any; +} + +/* Reads "/" for every id < len; sets cls[id] to 1 where the + * value is within pct percent of the highest, 0 otherwise (including + * ids with no value). Returns the highest id with a value plus one, or + * 0 when none had one. */ +static size_t cpu_class_by_value(const char *leaf, unsigned pct, + unsigned char *cls, size_t len) +{ + unsigned long best = 0; + size_t i, n = 0; + /* 32-bit is plenty: kHz clocks top out below 10^7 and capacities + * at 1024. On the heap, so the callers' frames stay within the + * 2 KiB thread-stack budget (PSP, GX). */ + unsigned *vals; + + if (len > CPU_CLASS_MAX_IDS) + len = CPU_CLASS_MAX_IDS; + if (!(vals = (unsigned*)malloc(len * sizeof(*vals) + 1))) + { + memset(cls, 0, len); + return 0; + } + for (i = 0; i < len; i++) + { + char path[256]; + snprintf(path, sizeof(path), CPU_CLASS_SYSFS "/cpu%u/%s", + (unsigned)i, leaf); + vals[i] = (unsigned)cpu_class_sysfs_ulong(path); + if (vals[i]) + { + n = i + 1; + if (vals[i] > best) + best = vals[i]; + } + } + for (i = 0; i < len; i++) + cls[i] = (vals[i] && vals[i] * 100 >= best * pct) ? 1 : 0; + free(vals); + return n; +} + +static size_t cpu_class_read(unsigned char *cls, size_t len) +{ + unsigned char atom[CPU_CLASS_MAX_IDS]; + size_t i, n; + + if (len > CPU_CLASS_MAX_IDS) + len = CPU_CLASS_MAX_IDS; + memset(cls, 0, len); + memset(atom, 0, sizeof(atom)); + + /* 1. Intel hybrid, as the kernel sees it (/sys/devices/cpu_core and + * cpu_atom, beside /sys/devices/system). Only meaningful when + * both kinds exist: cpu_core alone is what a homogeneous Intel + * part publishes. */ + if ( cpu_class_sysfs_cpulist(CPU_CLASS_SYSFS "/../../cpu_core/cpus", cls, len) + && cpu_class_sysfs_cpulist(CPU_CLASS_SYSFS "/../../cpu_atom/cpus", atom, len)) + { + n = 0; + for (i = 0; i < len; i++) + if (cls[i] || atom[i]) + n = i + 1; + return n; + } + memset(cls, 0, len); + + /* 2. Scheduler capacity (ARM), then 3. maximum clock. */ + if ((n = cpu_class_by_value("cpu_capacity", CPU_CLASS_CAPACITY_PCT, cls, len))) + return n; + return cpu_class_by_value("cpufreq/cpuinfo_max_freq", CPU_CLASS_FREQ_PCT, + cls, len); +} + +#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) + +#include + +typedef BOOL (WINAPI *cpu_class_get_cpusets_t)(void*, ULONG, ULONG*, + HANDLE, ULONG); + +/* SYSTEM_CPU_SET_INFORMATION, read by offset since older SDKs lack the + * type: Size at 0, Type at 4 (0 = CpuSet), Id at 8, Group at 12, + * LogicalProcessorIndex at 14, CoreIndex at 15, EfficiencyClass at 18. + * Ids are the processor's index within group 0, the numbering an + * affinity mask is built from; other groups are beyond a 64-bit mask + * and are left out. */ +static size_t cpu_class_read(unsigned char *cls, size_t len) +{ + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + cpu_class_get_cpusets_t getinfo = NULL; + unsigned char *buf = NULL; + ULONG len_needed = 0; + ULONG off; + size_t n = 0; + + memset(cls, 0, len); + if (k32) + getinfo = (cpu_class_get_cpusets_t)(void (*)(void)) + GetProcAddress(k32, "GetSystemCpuSetInformation"); + if (!getinfo) + return 0; + getinfo(NULL, 0, &len_needed, GetCurrentProcess(), 0); + if (!len_needed || !(buf = (unsigned char*)malloc(len_needed))) + return 0; + if (!getinfo(buf, len_needed, &len_needed, GetCurrentProcess(), 0)) + { + free(buf); + return 0; + } + for (off = 0; off + 20 <= len_needed; ) + { + DWORD size = *(DWORD*)(buf + off); + if (size < 20) + break; + if (*(DWORD*)(buf + off + 4) == 0 && *(WORD*)(buf + off + 12) == 0) + { + unsigned id = buf[off + 14]; + if (id < len) + { + cls[id] = buf[off + 18]; + if ((size_t)id + 1 > n) + n = id + 1; + } + } + off += size; + } + free(buf); + return n; +} + +#else + +/* No class source: inline so a platform whose callers are compiled + * out of the ranking does not carry an unreferenced function. */ +static INLINE size_t cpu_class_read(unsigned char *cls, size_t len) +{ + memset(cls, 0, len); + return 0; +} + +#endif + +#endif diff --git a/dependencies/libretro-common/features/features_cpu.c b/dependencies/libretro-common/features/features_cpu.c new file mode 100644 index 00000000..df6cae0f --- /dev/null +++ b/dependencies/libretro-common/features/features_cpu.c @@ -0,0 +1,1912 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (features_cpu.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#endif + +#include +#include +#include +#include "cpu_class.h" /* per-processor class, shared with rthreads */ +#if defined(__linux__) +#include +/* The prototype every Linux libc uses; spelled out so a strict C89 + * build, where glibc hides it, sees the same one. */ +extern long syscall(long number, ...); +#endif +#if defined(__linux__) && (defined(__arm__) || defined(__aarch64__)) +#if defined(HAVE_GETAUXVAL) || \ + (defined(__GLIBC__) && ((__GLIBC__ > 2) || \ + (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16))) || \ + (defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 18) +#define CPU_ARM_GETAUXVAL 1 +#include +#include +#include +#endif +#endif +#include +#include + +#if defined(_WIN32) && !defined(_XBOX) +#include +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) +#include +#endif +#endif + +#ifdef __PSL1GHT__ +#include +#endif + +#if defined(_XBOX360) +#include +#elif !defined(__MACH__) && !defined(__FreeBSD__) && (defined(__POWERPC__) || defined(__powerpc__) || defined(__ppc__) || defined(__PPC64__) || defined(__powerpc64__)) +#ifndef _PPU_INTRINSICS_H +#include +#endif +#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(ANDROID) || defined(__QNX__) || defined(DJGPP) +/* POSIX_MONOTONIC_CLOCK is not being defined in Android headers despite support being present. */ +#include +#endif + +#if defined(__QNX__) && !defined(CLOCK_MONOTONIC) +#define CLOCK_MONOTONIC 2 +#endif + +#if defined(PSP) +#include +#endif + +#if defined(PSP) || defined(__PSL1GHT__) +#include +#endif + +#if defined(PSP) +#include +#endif + +#if defined(VITA) +#include +#include +#endif + +#if defined(ORBIS) +#include +#endif + +#if defined(PS2) +#include +#endif + +#if !defined(__PSL1GHT__) && defined(__PS3__) +#include +#endif + +#if defined(GEKKO_NATIVE) +#include +#elif defined(GEKKO) +#include +#endif + +#ifdef WIIU +#include +#endif + +#if defined(HAVE_LIBNX) +#include +#endif + +#if defined(_3DS) +#include <3ds/svc.h> +#include <3ds/os.h> +#include <3ds/services/cfgu.h> +#endif + +#if defined(WEBOS) +#include +#endif + +/* iOS/OSX specific. Lacks clock_gettime(), so implement it. */ +#ifdef __MACH__ +#include +#include + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 0 +#endif + +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif + +/* clock_gettime() was added in iOS 10.0 / macOS 10.12 Sierra. + * On deployment targets below those versions the symbol doesn't + * exist in libSystem and the link fails, so fall back to a + * gettimeofday() shim. Gated on MIN_REQUIRED (deployment target) + * rather than MAX_ALLOWED (SDK) because the binary needs to run on + * the deployment target, and Apple weak-links clock_gettime when + * targeting < 10.12 even from a newer SDK. */ +#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) \ + && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000) \ + || (defined(MAC_OS_X_VERSION_MIN_REQUIRED) \ + && MAC_OS_X_VERSION_MIN_REQUIRED < 101200) +#define RA_NEEDS_CLOCK_GETTIME_SHIM 1 +static int ra_clock_gettime(int clk_ik, struct timespec *t) +{ + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) + return rv; + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; +} +#endif +#endif + +#if defined(__MACH__) && defined(RA_NEEDS_CLOCK_GETTIME_SHIM) +#else +#define ra_clock_gettime clock_gettime +#endif + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#if defined(BSD) || defined(__APPLE__) +#include +#endif + +#include + +retro_perf_tick_t cpu_features_get_perf_counter(void) +{ + retro_perf_tick_t time_ticks = 0; + /* The CPU's own cycle counter wherever there is one: a register + * read, no clock behind it. Platform clocks follow for the rest. */ +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + time_ticks = (retro_perf_tick_t)__rdtsc(); +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + { + unsigned a, d; + __asm__ __volatile__ ("rdtsc" : "=a" (a), "=d" (d)); + time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32); + } +#elif defined(__GNUC__) && defined(__aarch64__) + __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (time_ticks)); +#elif defined(__GNUC__) && defined(__ARM_ARCH_6__) + __asm__ __volatile__ ("mrc p15, 0, %0, c9, c13, 0" : "=r" (time_ticks)); +#elif defined(_WIN32) + { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + time_ticks = (retro_perf_tick_t)c.QuadPart; + } +#elif defined(GEKKO_NATIVE) + time_ticks = gk_ticks(); +#elif defined(GEKKO) + time_ticks = gettime(); +#elif !defined(__MACH__) && !defined(__FreeBSD__) && (defined(_XBOX360) || defined(__powerpc__) || defined(__ppc__) || defined(__POWERPC__) || defined(__PSL1GHT__) || defined(__PPC64__) || defined(__powerpc64__)) + time_ticks = __mftb(); +#elif (defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0) || defined(__QNX__) || defined(ANDROID) + { + struct timespec tv; + if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0) + time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 + + (retro_perf_tick_t)tv.tv_nsec; + } +#elif defined(PSP) || defined(VITA) + time_ticks = sceKernelGetSystemTimeWide(); +#elif defined(ORBIS) + sceRtcGetCurrentTick((SceRtcTick*)&time_ticks); +#elif defined(PS2) + time_ticks = ps2_clock(); +#elif defined(_3DS) + time_ticks = svcGetSystemTick(); +#elif defined(WIIU) + time_ticks = OSGetSystemTime(); +#elif defined(HAVE_LIBNX) + time_ticks = armGetSystemTick(); +#elif defined(__EMSCRIPTEN__) + time_ticks = emscripten_get_now() * 1000; +#endif + + return time_ticks; +} + +retro_time_t cpu_features_get_time_usec(void) +{ +#if defined(_WIN32) + static LARGE_INTEGER freq; + LARGE_INTEGER count; + + /* Frequency is guaranteed to not change. */ + if (!freq.QuadPart && !QueryPerformanceFrequency(&freq)) + return 0; + + if (!QueryPerformanceCounter(&count)) + return 0; + return (count.QuadPart / freq.QuadPart * 1000000) + (count.QuadPart % freq.QuadPart * 1000000 / freq.QuadPart); +#elif defined(__PSL1GHT__) + return sysGetSystemTime(); +#elif !defined(__PSL1GHT__) && defined(__PS3__) + return sys_time_get_system_time(); +#elif defined(GEKKO_NATIVE) + return gk_ticks_to_us(gk_ticks()); +#elif defined(GEKKO) + return ticks_to_microsecs(gettime()); +#elif defined(WIIU) + return ticks_to_us(OSGetSystemTime()); +#elif defined(SWITCH) || defined(HAVE_LIBNX) + return (svcGetSystemTick() * 10) / 192; +#elif defined(_3DS) + return osGetTime() * 1000; +#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(__QNX__) || defined(ANDROID) || defined(__MACH__) + struct timespec tv; + tv.tv_sec = 0; + tv.tv_nsec = 0; + if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) < 0) + return 0; + return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000; +#elif defined(__EMSCRIPTEN__) + return emscripten_get_now() * 1000; +#elif defined(PS2) + return ps2_clock() / PS2_CLOCKS_PER_MSEC * 1000; +#elif defined(VITA) || defined(PSP) + return sceKernelGetSystemTimeWide(); +#elif defined(DJGPP) + return uclock() * 1000000LL / UCLOCKS_PER_SEC; +#elif defined(ORBIS) + return sceKernelGetProcessTime(); +#else +#error "Your platform does not have a timer function implemented in cpu_features_get_time_usec(). Cannot continue." +#endif +} + +#if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__) || (defined(_M_X64) && _MSC_VER > 1310) || (defined(_M_IX86) && _MSC_VER > 1310) +#define CPU_X86 +#endif + +#if defined(_MSC_VER) && !defined(_XBOX) +#if (_MSC_VER > 1310) +#include +#endif +#endif + +#if defined(CPU_X86) && !defined(__MACH__) +#include +void x86_cpuid(uint32_t func, int32_t flags[4]) +{ + /* On Android, we compile RetroArch with PIC, and we + * are not allowed to clobber the ebx register. */ +#ifdef __x86_64__ +#define REG_b "rbx" +#define REG_S "rsi" +#else +#define REG_b "ebx" +#define REG_S "esi" +#endif + +#if defined(__GNUC__) + __asm__ volatile ( + "mov %%" REG_b ", %%" REG_S "\n" + "cpuid\n" + "xchg %%" REG_b ", %%" REG_S "\n" + : "=a"(flags[0]), "=S"(flags[1]), "=c"(flags[2]), "=d"(flags[3]) + : "a"(func)); +#elif defined(_MSC_VER) && INT_MAX == 2147483647 + __cpuid((int*)flags, func); +#else +#ifndef NDEBUG + printf("Unknown compiler. Cannot check CPUID with inline assembly.\n"); +#endif + memset(flags, 0, 4 * sizeof(int)); +#endif +} + +/* Only runs on i686 and above. Needs to be conditionally run. */ +static uint64_t xgetbv_x86(uint32_t idx) +{ +#if defined(__GNUC__) + uint32_t eax, edx; + __asm__ volatile ( + /* Older GCC versions (Apple's GCC for example) do + * not understand xgetbv instruction. + * Stamp out the machine code directly. + */ + ".byte 0x0f, 0x01, 0xd0\n" + : "=a"(eax), "=d"(edx) : "c"(idx)); + return ((uint64_t)edx << 32) | eax; +#elif _MSC_FULL_VER >= 160040219 + /* Intrinsic only works on 2010 SP1 and above. */ + return _xgetbv(idx); +#else +#ifndef NDEBUG + printf("Unknown compiler. Cannot check xgetbv bits.\n"); +#endif + return 0; +#endif +} +#endif + +/* RunFast mode is a 32-bit VFP control, and this writes FPSCR - so it + * needs both an ARM32 target and an FPU worth configuring. NEON + * implies VFP, which is why its presence is the proxy used here; a + * VFP-less core such as the armv5te arm926ej-s would fault on the + * write. + * + * CPU_ARM_RUNFAST is the single condition for the definition AND + * every call. They were separate conditions and drifted: the calls + * ended up reachable where the definition was not, which is a link + * error on exactly the targets with no NEON - Miyoo armv5te and + * armv7 builds without an FPU selected. */ +#if (defined(__ARM_NEON) || defined(__ARM_NEON__)) && defined(__arm__) +#define CPU_ARM_RUNFAST 1 +#endif + +#ifdef CPU_ARM_RUNFAST +static void arm_enable_runfast_mode(void) +{ + /* RunFast mode. Enables flush-to-zero and some + * floating point optimizations. */ + static const unsigned x = 0x04086060; + static const unsigned y = 0x03000000; + int r; + __asm__ volatile( + "fmrx %0, fpscr \n\t" /* r0 = FPSCR */ + "and %0, %0, %1 \n\t" /* r0 = r0 & 0x04086060 */ + "orr %0, %0, %2 \n\t" /* r0 = r0 | 0x03000000 */ + "fmxr fpscr, %0 \n\t" /* FPSCR = r0 */ + : "=r"(r) + : "r"(x), "r"(y) + ); +} +#endif /* CPU_ARM_RUNFAST */ + +#if defined(__linux__) && !defined(CPU_X86) +static unsigned char check_arm_cpu_feature(const char* feature) +{ + char line[1024]; + unsigned char status = 0; + FILE *fp; + +#ifdef CPU_ARM_GETAUXVAL + unsigned long mask = 0; + unsigned long caps; + unsigned long type = AT_HWCAP; +#if defined(__aarch64__) +#ifdef HWCAP_ASIMD + if (!strcmp(feature, "asimd")) + mask = HWCAP_ASIMD; +#endif +#ifdef HWCAP_AES + if (!strcmp(feature, "aes")) + mask = HWCAP_AES; +#endif +#ifdef HWCAP_CRC32 + if (!strcmp(feature, "crc32")) + mask = HWCAP_CRC32; +#endif +#ifdef HWCAP_SHA1 + if (!strcmp(feature, "sha1")) + mask = HWCAP_SHA1; +#endif +#ifdef HWCAP_SHA2 + if (!strcmp(feature, "sha2")) + mask = HWCAP_SHA2; +#endif +#ifdef HWCAP_SHA512 + if (!strcmp(feature, "sha512")) + mask = HWCAP_SHA512; +#endif +#else +#ifdef HWCAP_NEON + if (!strcmp(feature, "neon")) + mask = HWCAP_NEON; +#endif +#ifdef HWCAP_VFPv3 + if (!strcmp(feature, "vfpv3")) + mask = HWCAP_VFPv3; +#endif +#ifdef HWCAP_VFPv4 + if (!strcmp(feature, "vfpv4")) + mask = HWCAP_VFPv4; +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_AES) + if (!strcmp(feature, "aes")) + { + mask = HWCAP2_AES; + type = AT_HWCAP2; + } +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_CRC32) + if (!strcmp(feature, "crc32")) + { + mask = HWCAP2_CRC32; + type = AT_HWCAP2; + } +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_SHA1) + if (!strcmp(feature, "sha1")) + { + mask = HWCAP2_SHA1; + type = AT_HWCAP2; + } +#endif +#if defined(AT_HWCAP2) && defined(HWCAP2_SHA2) + if (!strcmp(feature, "sha2")) + { + mask = HWCAP2_SHA2; + type = AT_HWCAP2; + } +#endif +#endif + if (mask) + { + errno = 0; + caps = getauxval(type); + if (errno == 0) + return (caps & mask) != 0; + } +#endif + + fp = fopen("/proc/cpuinfo", "r"); + + if (!fp) + return 0; + + while (fgets(line, sizeof(line), fp)) + { + const char *list; + const char *p; + size_t flen; + + if (strncmp(line, "Features\t: ", 11)) + continue; + + /* Feature names are space separated and several are prefixes of + * others - 'aes' sits inside 'sveaes', 'sha3' inside 'svesha3', + * 'asimd' inside 'asimddp' - so compare whole tokens. */ + flen = strlen(feature); + list = line + 11; + + for (p = list; (p = strstr(p, feature)); p += flen) + { + char after = p[flen]; + char before = (p == list) ? ' ' : p[-1]; + if ( (before == ' ') + && (after == ' ' || after == '\n' || after == '\0')) + { + status = 1; + break; + } + } + + break; + } + + fclose(fp); + + return status; +} + +#if !defined(_SC_NPROCESSORS_ONLN) +/** + * parse_decimal: + * + * Parse an decimal integer starting from 'input', but not going further + * than 'limit'. Return the value into '*result'. + * + * NOTE: Does not skip over leading spaces, or deal with sign characters. + * NOTE: Ignores overflows. + * + * The function returns NULL in case of error (bad format), or the new + * position after the decimal number in case of success (which will always + * be <= 'limit'). + * + * Leaf function. + **/ +static const char *parse_decimal(const char* input, + const char* limit, int* result) +{ + const char* p = input; + int val = 0; + + while (p < limit) + { + int d = (*p - '0'); + if ((unsigned)d >= 10U) + break; + val = val*10 + d; + p++; + } + if (p == input) + return NULL; + + *result = val; + return p; +} + +/** + * cpulist_parse: + * Parse a textual list of cpus and store the result inside a CpuList object. + * Input format is the following: + * - comma-separated list of items (no spaces) + * - each item is either a single decimal number (cpu index), or a range made + * of two numbers separated by a single dash (-). Ranges are inclusive. + * + * Examples: 0 + * 2,4-127,128-143 + * 0-1 + **/ +static void cpulist_parse(CpuList* list, const char *buf, ssize_t len) +{ + const char* p = buf; + const char* end = p + len; + + /* NOTE: the input line coming from sysfs typically contains a + * trailing newline, so take care of it in the code below + */ + while (p < end && *p != '\n') + { + int val, start_value, end_value; + /* Find the end of current item, and put it into 'q' */ + const char *q = (const char*)memchr(p, ',', end-p); + + if (!q) + q = end; + + /* Get first value */ + if (!(p = parse_decimal(p, q, &start_value))) + return; + + end_value = start_value; + + /* If we're not at the end of the item, expect a dash and + * and integer; extract end value. + */ + if (p < q && *p == '-') + { + if (!(p = parse_decimal(p+1, q, &end_value))) + return; + } + + /* Set bits CPU list bits */ + for (val = start_value; val <= end_value; val++) + { + if ((unsigned)val < 32) + list->mask |= (uint32_t)(UINT32_C(1) << val); + } + + /* Jump to next item */ + p = q; + if (p < end) + p++; + } +} + +/** + * cpulist_read_from: + * + * Read a CPU list from one sysfs file + **/ +static void cpulist_read_from(CpuList* list, const char* filename) +{ + char buf[512]; + size_t _len; + FILE *fp = fopen(filename, "r"); + + list->mask = 0; + + if (!fp) + return; + + _len = fread(buf, 1, sizeof(buf) - 1, fp); + fclose(fp); + buf[_len] = '\0'; + + cpulist_parse(list, buf, (ssize_t)_len); +} +#endif + +#endif + +#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) +/* GetLogicalProcessorInformation arrived in XP SP3 and Server 2003, so + * it is resolved rather than linked: on Win9x, NT 4 and 2000 the export + * is absent, GetProcAddress reports so, and the caller falls back to + * the plain processor count. The Ex form carrying EfficiencyClass is + * Windows 7 and later and would rank efficiency cores, which this does + * not attempt. */ +typedef BOOL (WINAPI *cpu_glpi_t)( + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); + +/* Caller frees. Returns NULL, leaving *count untouched, wherever the + * export or the query is unavailable. */ +static SYSTEM_LOGICAL_PROCESSOR_INFORMATION *cpu_win32_slpi(DWORD *count) +{ + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = NULL; + DWORD _len = 0; + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + cpu_glpi_t glpi = k32 ? (cpu_glpi_t)(void (*)(void)) + GetProcAddress(k32, "GetLogicalProcessorInformation") : NULL; + + if (!glpi) + return NULL; + + /* The first call is expected to fail, and reports the size wanted. */ + if (glpi(NULL, &_len)) + return NULL; + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || _len == 0) + return NULL; + if (_len % sizeof(*buf)) + return NULL; + + if (!(buf = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)malloc(_len))) + return NULL; + + if (!glpi(buf, &_len)) + { + free(buf); + return NULL; + } + + *count = _len / (DWORD)sizeof(*buf); + return buf; +} + +/* Size in KiB of the level-3 cache whose ProcessorMask covers the + * processor, from the same GetLogicalProcessorInformation records + * (RelationCache entries carry a level, a size and the mask of + * processors behind them); 0 where none does. */ +static unsigned cpu_win32_llc_kib( + const SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf, DWORD count, + unsigned bit) +{ + DWORD i; + for (i = 0; i < count; i++) + { + if (buf[i].Relationship != RelationCache) + continue; + if (buf[i].Cache.Level != 3) + continue; + if (buf[i].ProcessorMask & (((ULONG_PTR)1) << bit)) + return (unsigned)(buf[i].Cache.Size / 1024); + } + return 0; +} +#endif + +#if defined(__linux__) +/* Number of distinct SMT sibling groups under + * /sys/devices/system/cpu, which is one per physical core. The list a + * sibling file holds names every processor sharing that core, so the + * lowest id in it identifies the group and counting distinct ones + * counts cores. */ +static unsigned linux_core_amount_physical(unsigned logical) +{ + char path[512]; + char line[64]; + unsigned seen[128]; + unsigned n_seen = 0; + unsigned found = 0; + unsigned i; + + /* One entry per core, so a machine wider than the table is left to + * the logical count rather than answered wrongly. */ + if (logical > (unsigned)(sizeof(seen) / sizeof(seen[0]))) + return logical; + + for (i = 0; i < 256 && found < logical; i++) + { + FILE *fp; + unsigned first; + unsigned j; + + snprintf(path, sizeof(path), + CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i); + + if (!(fp = fopen(path, "r"))) + continue; + + line[0] = '\0'; + if (!fgets(line, sizeof(line), fp)) + { + fclose(fp); + continue; + } + fclose(fp); + + found++; + + if (sscanf(line, "%u", &first) != 1) + continue; + + for (j = 0; j < n_seen; j++) + if (seen[j] == first) + break; + + if (j == n_seen && n_seen < (unsigned)(sizeof(seen) / sizeof(seen[0]))) + seen[n_seen++] = first; + } + + /* Nothing readable, so the kernel is not publishing topology here. */ + if (n_seen == 0) + return logical; + + return n_seen; +} +#endif + +#if defined(__linux__) +/* First unsigned in a one-line sysfs file, or @fallback where the file + * is missing or unreadable. */ +static unsigned sysfs_read_uint(const char *path, unsigned fallback) +{ + char line[64]; + unsigned val; + FILE *fp = fopen(path, "r"); + + if (!fp) + return fallback; + + line[0] = '\0'; + if (!fgets(line, sizeof(line), fp)) + { + fclose(fp); + return fallback; + } + fclose(fp); + + if (sscanf(line, "%u", &val) != 1) + return fallback; + return val; +} + +/* Size of the last-level cache the processor sits behind, in KiB, + * from "/cache/index3/size" (a figure with a K or M suffix); 0 + * where the kernel publishes none. What separates the two CCDs of an + * X3D part, which are one class and near enough one clock. */ +static unsigned sysfs_read_llc_kib(unsigned cpu) +{ + char path[512]; + char line[64]; + unsigned val = 0; + char unit = 0; + FILE *fp; + + snprintf(path, sizeof(path), CPU_CLASS_SYSFS "/cpu%u/cache/index3/size", cpu); + if (!(fp = fopen(path, "r"))) + return 0; + line[0] = '\0'; + if (!fgets(line, sizeof(line), fp)) + line[0] = '\0'; + fclose(fp); + if (sscanf(line, "%u%c", &val, &unit) < 1) + return 0; + if (unit == 'M' || unit == 'm') + return val * 1024; + if (unit == 'G' || unit == 'g') + return val * 1024 * 1024; + return val; +} +#endif + +#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__) +struct cpu_proc_rank +{ + unsigned cls; /* performance class from cpu_class.h, higher is faster */ + unsigned llc; /* last-level cache behind the core, KiB */ + unsigned freq; /* kHz, higher is a stronger core within its class */ + unsigned id; /* OS processor identifier */ + unsigned smt; /* 0 for the first processor on its core, else 1 */ +}; + +/* Fastest class first (the P-cores, the big cluster); within a class + * the bigger last-level cache first, then the higher clock; a core + * ahead of its own SMT siblings; and the identifier as the tie-break + * so the result does not depend on the order the entries were + * gathered in. Class comes before everything so an E-core that + * happens to clock above a P-core sibling cannot outrank the fast + * silicon. Cache comes before clock for the X3D parts: the V-cache + * CCD boosts a few percent lower than the other and is the die an + * emulator wants to be on. */ +static int cpu_proc_rank_cmp(const void *a, const void *b) +{ + const struct cpu_proc_rank *l = (const struct cpu_proc_rank *)a; + const struct cpu_proc_rank *r = (const struct cpu_proc_rank *)b; + + if (l->cls != r->cls) + return (l->cls > r->cls) ? -1 : 1; + if (l->llc != r->llc) + return (l->llc > r->llc) ? -1 : 1; + if (l->freq != r->freq) + return (l->freq > r->freq) ? -1 : 1; + if (l->smt != r->smt) + return (l->smt < r->smt) ? -1 : 1; + if (l->id != r->id) + return (l->id < r->id) ? -1 : 1; + return 0; +} +#endif + +/* The order, restricted to processors whose bit is set in allowed + * (NULL: no restriction). Split from the public function so a test + * can rank a fixture topology under a synthetic affinity mask. */ +static size_t cpu_features_processor_order_masked( + const unsigned char *allowed, unsigned *s, size_t len) +{ + size_t n = 0; +#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__) + unsigned char klass[CPU_CLASS_MAX_IDS]; + size_t n_class = cpu_class_read(klass, sizeof(klass)); +#define CPU_PROC_CLASS(id) \ + (((size_t)(id) < n_class) ? klass[(id)] : 0) +#define CPU_PROC_ALLOWED(id) \ + (!allowed || ((size_t)(id) < CPU_CLASS_MAX_IDS && allowed[(id)])) +#endif + + if (!s || !len) + return 0; + +#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) + { + DWORD count = 0; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = cpu_win32_slpi(&count); + + if (buf) + { + struct cpu_proc_rank *rank; + size_t cap = (size_t)cpu_features_get_core_amount(); + + if (cap < 1) + cap = 1; + if (cap > 1024) + cap = 1024; + + if ((rank = (struct cpu_proc_rank *) + malloc(cap * sizeof(struct cpu_proc_rank)))) + { + DWORD i; + for (i = 0; i < count && n < cap; i++) + { + unsigned bit; + unsigned seen = 0; + ULONG_PTR mask = buf[i].ProcessorMask; + + if (buf[i].Relationship != RelationProcessorCore) + continue; + + /* One mask bit per processor on this core, the lowest + * of them being the one an SMT sibling shares with. */ + for (bit = 0; bit < sizeof(ULONG_PTR) * 8 && n < cap; bit++) + { + if (!(mask & (((ULONG_PTR)1) << bit))) + continue; + if (!CPU_PROC_ALLOWED(bit)) + { + seen++; + continue; + } + rank[n].cls = CPU_PROC_CLASS(bit); + rank[n].llc = cpu_win32_llc_kib(buf, count, bit); + rank[n].freq = 0; + rank[n].id = bit; + rank[n].smt = seen ? 1 : 0; + seen++; + n++; + } + } + + if (n) + { + qsort(rank, n, sizeof(struct cpu_proc_rank), + cpu_proc_rank_cmp); + if (n > len) + n = len; + for (i = 0; i < (DWORD)n; i++) + s[i] = rank[i].id; + } + free(rank); + } + free(buf); + + if (n) + return n; + } + } +#endif + +#if defined(__linux__) + { + struct cpu_proc_rank *rank; + char path[512]; + unsigned i; + /* Every processor is ranked before any is handed back: gathering + * only the first @len of them would sort a set chosen by + * identifier and hand back the weakest cores on a layout that + * numbers the little cluster first. */ + size_t cap = 1024; /* every processor the kernel publishes; 16 KiB */ + + if (!(rank = (struct cpu_proc_rank *) + malloc(cap * sizeof(struct cpu_proc_rank)))) + return 0; + + for (i = 0; i < 1024 && n < cap; i++) + { + unsigned first; + + snprintf(path, sizeof(path), + CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i); + /* A processor with no sibling list is one the kernel is not + * publishing, rather than one that shares no core. */ + first = sysfs_read_uint(path, (unsigned)-1); + if (first == (unsigned)-1) + continue; + if (!CPU_PROC_ALLOWED(i)) + continue; + + snprintf(path, sizeof(path), + CPU_CLASS_SYSFS "/cpu%u/cpufreq/cpuinfo_max_freq", i); + + rank[n].cls = CPU_PROC_CLASS(i); + rank[n].llc = sysfs_read_llc_kib(i); + rank[n].freq = sysfs_read_uint(path, 0); + rank[n].id = i; + rank[n].smt = (i == first) ? 0 : 1; + n++; + } + + if (n) + { + qsort(rank, n, sizeof(struct cpu_proc_rank), cpu_proc_rank_cmp); + if (n > len) + n = len; + for (i = 0; i < (unsigned)n; i++) + s[i] = rank[i].id; + } + + free(rank); + + if (n) + return n; + } +#endif + + /* No topology to rank by, so name each processor once in order. */ + { + unsigned amount = cpu_features_get_core_amount(); + unsigned i; + for (i = 0; n < len && i < amount; i++) + { + if (allowed && (i >= CPU_CLASS_MAX_IDS || !allowed[i])) + continue; + s[n++] = i; + } + } +#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__) +#undef CPU_PROC_CLASS +#undef CPU_PROC_ALLOWED +#endif + return n; +} + +size_t cpu_features_get_processor_order(unsigned *s, size_t len) +{ +#if defined(__linux__) || (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) + unsigned char allowed[CPU_CLASS_MAX_IDS]; +#endif + const unsigned char *mask = NULL; +#if defined(__linux__) + { + /* The processors this thread may run on: a pin from a parent + * process, a container or the user is a boundary, not something + * to hand back as a target. */ + unsigned long bits[CPU_CLASS_MAX_IDS / (8 * sizeof(unsigned long))]; + memset(bits, 0, sizeof(bits)); + if (syscall(__NR_sched_getaffinity, 0, sizeof(bits), bits) > 0) + { + size_t i; + for (i = 0; i < CPU_CLASS_MAX_IDS; i++) + allowed[i] = (unsigned char)((bits[i / (8 * sizeof(unsigned long))] + >> (i % (8 * sizeof(unsigned long)))) & 1ul); + mask = allowed; + } + } +#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) + { + DWORD_PTR proc = 0, sys = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys) && proc) + { + size_t i; + memset(allowed, 0, sizeof(allowed)); + for (i = 0; i < sizeof(DWORD_PTR) * 8; i++) + allowed[i] = (unsigned char)((proc >> i) & 1); + mask = allowed; + } + } +#endif + return cpu_features_processor_order_masked(mask, s, len); +} + +unsigned cpu_features_get_core_amount_physical(void) +{ + unsigned logical = cpu_features_get_core_amount(); + +#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) + { + DWORD count = 0; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = cpu_win32_slpi(&count); + + if (buf) + { + DWORD i; + unsigned cores = 0; + for (i = 0; i < count; i++) + if (buf[i].Relationship == RelationProcessorCore) + cores++; + free(buf); + if (cores > 0) + return cores; + } + } +#elif defined(__APPLE__) + { + /* Darwin publishes both counts, so the physical one is a read + * rather than a derivation. */ + int val = 0; + size_t _len = sizeof(val); + if ( sysctlbyname("hw.physicalcpu", &val, &_len, NULL, 0) == 0 + && val > 0) + return (unsigned)val; + } +#elif defined(__linux__) + return linux_core_amount_physical(logical); +#endif + + /* Every other target either has no SMT to discount or publishes no + * way to tell, and the thread count is the safe answer in both + * cases. */ + return logical; +} + +unsigned cpu_features_get_core_amount(void) +{ +#if defined(_WIN32) && !defined(_XBOX) + /* Win32 */ + SYSTEM_INFO sysinfo; +#if defined(__WINRT__) || (defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + GetNativeSystemInfo(&sysinfo); +#else + GetSystemInfo(&sysinfo); +#endif + return sysinfo.dwNumberOfProcessors; +#elif defined(GEKKO) + return 1; +#elif defined(PSP) || defined(PS2) + return 1; +#elif defined(__PSL1GHT__) || !defined(__PSL1GHT__) && defined(__PS3__) + return 1; /* Only one PPU, SPUs don't really count */ +#elif defined(VITA) + return 4; +#elif defined(HAVE_LIBNX) || defined(SWITCH) + return 4; +#elif defined(_3DS) + u8 device_model = 0xFF; + CFGU_GetSystemModel(&device_model);/*(0 = O3DS, 1 = O3DSXL, 2 = N3DS, 3 = 2DS, 4 = N3DSXL, 5 = N2DSXL)*/ + switch (device_model) + { + case 0: + case 1: + case 3: + /*Old 3/2DS*/ + return 2; + + case 2: + case 4: + case 5: + /*New 3/2DS*/ + return 4; + + default: + /*Unknown Device Or Check Failed*/ + break; + } + return 1; +#elif defined(WIIU) + return 3; +#elif defined(_SC_NPROCESSORS_ONLN) + /* Linux, most UNIX-likes. */ + long ret = sysconf(_SC_NPROCESSORS_ONLN); + if (ret <= 0) + return (unsigned)1; + return (unsigned)ret; +#elif defined(BSD) || defined(__APPLE__) + /* BSD */ + /* Copypasta from stackoverflow, dunno if it works. */ + int num_cpu = 0; + int mib[4]; + size_t _len = sizeof(num_cpu); + + mib[0] = CTL_HW; + mib[1] = HW_AVAILCPU; + sysctl(mib, 2, &num_cpu, &_len, NULL, 0); + if (num_cpu < 1) + { + mib[1] = HW_NCPU; + sysctl(mib, 2, &num_cpu, &_len, NULL, 0); + if (num_cpu < 1) + num_cpu = 1; + } + return num_cpu; +#elif defined(__linux__) + CpuList cpus_present[1]; + CpuList cpus_possible[1]; + int amount = 0; + + cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present"); + cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible"); + + /* Compute the intersection of both sets to get the actual number of + * CPU cores that can be used on this device by the kernel. + */ + cpus_present->mask &= cpus_possible->mask; + amount = __builtin_popcount(cpus_present->mask); + + if (amount == 0) + return 1; + return amount; +#elif defined(_XBOX360) + return 3; +#else + /* No idea, assume single core. */ + return 1; +#endif +} + +/* According to http://en.wikipedia.org/wiki/CPUID */ +#define VENDOR_INTEL_b 0x756e6547 +#define VENDOR_INTEL_c 0x6c65746e +#define VENDOR_INTEL_d 0x49656e69 + +#if defined(__MACH__) && defined(CPU_X86) +/* Whole-token search of one of the machdep.cpu.*features sysctls, each + * of which is a space separated list of CPUID feature names. */ +static bool darwin_cpu_feature_present(const char *key, const char *want) +{ + char buf[1024]; + size_t len = sizeof(buf); + size_t wlen = strlen(want); + const char *p; + + buf[0] = '\0'; + if (sysctlbyname(key, buf, &len, NULL, 0) != 0) + return false; + buf[sizeof(buf) - 1] = '\0'; + + for (p = buf; (p = strstr(p, want)); p += wlen) + { + char after = p[wlen]; + char before = (p == buf) ? ' ' : p[-1]; + if ((before == ' ') && (after == ' ' || after == '\0')) + return true; + } + return false; +} +#endif + +static uint64_t cpu_features_probe(void) +{ + uint64_t cpu = 0; +#if defined(CPU_X86) && !defined(__MACH__) + int vendor_is_intel = 0; + /* Set once the OS is known to preserve YMM state, which AVX, FMA3 + * and FMA4 all depend on. */ + int ymm_state = 0; + const int avx_flags = (1 << 27) | (1 << 28); +#endif +#if defined(__MACH__) + /* sysctlbyname() returns 0 (success) whenever the key exists, regardless + * of its value. On Intel Macs the hw.optional.* keys are always present + * but report 0 when the feature is unsupported (e.g. avx512f on pre-Skylake + * Xeon CPUs). We therefore have to read the value, not just the success + * code. */ + int _val = 0; + size_t _len = sizeof(_val); + if ( sysctlbyname("hw.optional.floatingpoint", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_CMOV; + +#if defined(CPU_X86) + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.mmx", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_MMX | RETRO_SIMD_MMXEXT; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.sse", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_SSE; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.sse2", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_SSE2; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.sse3", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_SSE3; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.supplementalsse3", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_SSSE3; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.sse4_1", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_SSE4; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.sse4_2", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_SSE42; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.aes", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_AES; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.avx1_0", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_AVX; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.fma", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_FMA3; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.avx2_0", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_AVX2; + _val = 0; + _len = sizeof(_val); + /* RETRO_SIMD_AVX512 means the x86-64-v4 baseline (F, CD, BW, DQ, + * VL), so every one of those keys has to answer. Darwin enables ZMM + * state lazily on first use, which is why these sysctls and not + * xgetbv decide it here. */ + { + static const char *avx512_keys[] = { + "hw.optional.avx512f", "hw.optional.avx512cd", + "hw.optional.avx512bw", "hw.optional.avx512dq", + "hw.optional.avx512vl" + }; + size_t k; + int have_avx512 = 1; + for (k = 0; k < sizeof(avx512_keys) / sizeof(avx512_keys[0]); k++) + { + _val = 0; + _len = sizeof(_val); + if (!(sysctlbyname(avx512_keys[k], &_val, &_len, NULL, 0) == 0 && _val)) + { + have_avx512 = 0; + break; + } + } + if (have_avx512) + cpu |= RETRO_SIMD_AVX512; + } + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.altivec", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_VMX; + /* Darwin publishes no hw.optional key for PCLMULQDQ, so read the + * CPUID leaf-1 feature-name list instead. Matching is on whole + * space-separated tokens: a plain strstr() would also fire on a + * hypothetical future "PCLMULQDQ2". */ + if (darwin_cpu_feature_present("machdep.cpu.features", "PCLMULQDQ")) + cpu |= RETRO_SIMD_PCLMUL; + /* SHA-NI lives in the leaf-7 list rather than the leaf-1 one. */ + if (darwin_cpu_feature_present("machdep.cpu.leaf7_features", "SHA")) + cpu |= RETRO_SIMD_SHA1 | RETRO_SIMD_SHA256; +#else + _val = 0; + _len = sizeof(_val); + /* Older key first; newer systems also carry the FEAT_ spelling. + * Written out rather than folded into one condition with a comma + * operator: the second query needs the buffer and its length reset + * first, and doing that inside a short-circuit || is both a warning + * and a thing to read twice. */ + if (sysctlbyname("hw.optional.armv8_crc32", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_CRC32; + else + { + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.arm.FEAT_CRC32", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_CRC32; + } + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.arm.FEAT_AES", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_AES; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.arm.FEAT_SHA512", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_SHA512; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.arm.FEAT_SHA1", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_SHA1; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.arm.FEAT_SHA256", &_val, &_len, NULL, 0) == 0 + && _val) + cpu |= RETRO_SIMD_SHA256; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.neon", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_NEON; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.neon_fp16", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_VFPV3; + _val = 0; + _len = sizeof(_val); + if (sysctlbyname("hw.optional.neon_hpfp", &_val, &_len, NULL, 0) == 0 && _val) + cpu |= RETRO_SIMD_VFPV4; +#endif +#elif defined(_XBOX1) + cpu |= RETRO_SIMD_MMX | RETRO_SIMD_SSE | RETRO_SIMD_MMXEXT; +#elif defined(CPU_X86) + unsigned max_flag = 0; + int32_t flags[4]; + int vendor_shuffle[3]; + char vendor[13]; + x86_cpuid(0, flags); + vendor_shuffle[0] = flags[1]; + vendor_shuffle[1] = flags[3]; + vendor_shuffle[2] = flags[2]; + + vendor[0] = '\0'; + memcpy(vendor, vendor_shuffle, sizeof(vendor_shuffle)); + + /* printf("[CPUID]: Vendor: %s\n", vendor); */ + + vendor_is_intel = ( + flags[1] == VENDOR_INTEL_b + && flags[2] == VENDOR_INTEL_c + && flags[3] == VENDOR_INTEL_d); + + /* CPUID register contents are unsigned; flags[] is int32_t, so the + * widening is spelled out rather than left implicit -- leaf + * 0x80000000 legitimately returns values with the top bit set. */ + max_flag = (uint32_t)flags[0]; + /* Does CPUID not support func = 1? (unlikely ...) */ + if (max_flag < 1) + return 0; + + x86_cpuid(1, flags); + + if (flags[3] & (1 << 15)) + cpu |= RETRO_SIMD_CMOV; + + if (flags[3] & (1 << 23)) + cpu |= RETRO_SIMD_MMX; + + /* SSE also implies MMXEXT (according to FFmpeg source). */ + if (flags[3] & (1 << 25)) + cpu |= RETRO_SIMD_SSE | RETRO_SIMD_MMXEXT; + + if (flags[3] & (1 << 26)) + cpu |= RETRO_SIMD_SSE2; + + if (flags[2] & (1 << 0)) + cpu |= RETRO_SIMD_SSE3; + + if (flags[2] & (1 << 9)) + cpu |= RETRO_SIMD_SSSE3; + + if (flags[2] & (1 << 19)) + cpu |= RETRO_SIMD_SSE4; + + if (flags[2] & (1 << 20)) + cpu |= RETRO_SIMD_SSE42; + + if ((flags[2] & (1 << 23))) + cpu |= RETRO_SIMD_POPCNT; + + if (vendor_is_intel && (flags[2] & (1 << 22))) + cpu |= RETRO_SIMD_MOVBE; + + if (flags[2] & (1 << 25)) + cpu |= RETRO_SIMD_AES; + + if (flags[2] & (1 << 1)) + cpu |= RETRO_SIMD_PCLMUL; + + /* Must only perform xgetbv check if we have + * AVX CPU support (guaranteed to have at least i686). */ + if (((flags[2] & avx_flags) == avx_flags) + && ((xgetbv_x86(0) & 0x6) == 0x6)) + { + ymm_state = 1; + cpu |= RETRO_SIMD_AVX; + } + + /* FMA3 accumulates in YMM, so it answers only where the OS keeps + * that state. Leaf 1 ECX is still in flags here; the extended leaf + * carrying FMA4 is read further down. */ + if (ymm_state && (flags[2] & (1 << 12))) + cpu |= RETRO_SIMD_FMA3; + + if (max_flag >= 7) + { + /* CPUID leaf 7 sub-leaf 0 requires ECX = 0. + * x86_cpuid() does not set ECX, so call cpuid directly. */ + int32_t flags7[4]; +#if defined(__GNUC__) + __asm__ volatile ( + "mov %%" REG_b ", %%" REG_S "\n" + "cpuid\n" + "xchg %%" REG_b ", %%" REG_S "\n" + : "=a"(flags7[0]), "=S"(flags7[1]), "=c"(flags7[2]), "=d"(flags7[3]) + : "a"(7), "c"(0)); +#elif defined(_MSC_VER) && INT_MAX == 2147483647 +#if _MSC_VER >= 1600 + __cpuidex((int*)flags7, 7, 0); +#else + { + int *p = (int*)flags7; + __asm { + mov eax, 7 + xor ecx, ecx + cpuid + mov esi, p + mov [esi], eax + mov [esi + 4], ebx + mov [esi + 8], ecx + mov [esi + 12], edx + } + } +#endif +#else + memset(flags7, 0, sizeof(flags7)); +#endif + + /* AVX2 executes on YMM registers, so like AVX it needs the OS + * to save that state, not just the CPUID bit. */ + if (ymm_state && (flags7[1] & (1 << 5))) + cpu |= RETRO_SIMD_AVX2; + + /* SHA-NI is one bit covering both digests. */ + if (flags7[1] & (1 << 29)) + cpu |= RETRO_SIMD_SHA1 | RETRO_SIMD_SHA256; + + /* AVX-512 means the x86-64-v4 baseline: leaf 7 sub-leaf 0 EBX + * bits 16 (F), 17 (DQ), 28 (CD), 30 (BW) and 31 (VL). Code that + * checks this one flag may then use any of them; that is every + * AVX-512 CPU except the Xeon Phi parts, which lack BW/DQ/VL. + * + * The OS must also save opmask and ZMM state (XCR0 bits 5,6,7 on + * top of 1,2). xgetbv is only valid once OSXSAVE is set, and + * ymm_state already proved that, so test it first: without it + * xgetbv faults on a CPU that has AVX-512 but an OS that never + * enabled XSAVE. */ + { + const uint32_t avx512_v4 = (1u << 16) | (1u << 17) + | (1u << 28) | (1u << 30) | (1u << 31); + if ( ymm_state + && (((uint32_t)flags7[1] & avx512_v4) == avx512_v4) + && ((xgetbv_x86(0) & 0xe6) == 0xe6)) + cpu |= RETRO_SIMD_AVX512; + } + + /* Leaf 7 EAX carries the highest sub-leaf the CPU implements, so + * consult it before asking for sub-leaf 1: an out of range + * sub-leaf returns zeroes on current parts but is not documented + * to. */ + if (flags7[0] >= 1) + { + int32_t flags71[4]; +#if defined(__GNUC__) + __asm__ volatile ( + "mov %%" REG_b ", %%" REG_S "\n" + "cpuid\n" + "xchg %%" REG_b ", %%" REG_S "\n" + : "=a"(flags71[0]), "=S"(flags71[1]), "=c"(flags71[2]), "=d"(flags71[3]) + : "a"(7), "c"(1)); +#elif defined(_MSC_VER) && INT_MAX == 2147483647 +#if _MSC_VER >= 1600 + __cpuidex((int*)flags71, 7, 1); +#else + { + int *p = (int*)flags71; + __asm { + mov eax, 7 + mov ecx, 1 + cpuid + mov esi, p + mov [esi], eax + mov [esi + 4], ebx + mov [esi + 8], ecx + mov [esi + 12], edx + } + } +#endif +#else + memset(flags71, 0, sizeof(flags71)); +#endif + + if (flags71[0] & (1 << 0)) + cpu |= RETRO_SIMD_SHA512; + } + } + + x86_cpuid(0x80000000, flags); + max_flag = (uint32_t)flags[0]; + if (max_flag >= 0x80000001u) + { + x86_cpuid(0x80000001, flags); + if (flags[3] & (1 << 23)) + cpu |= RETRO_SIMD_MMX; + if (flags[3] & (1 << 22)) + cpu |= RETRO_SIMD_MMXEXT; + /* LZCNT / ABM (ECX bit 5). macOS x86 uses the sysctl path above and + * does not report this bit; consumers there fall back accordingly. */ + if (flags[2] & (1 << 5)) + cpu |= RETRO_SIMD_LZCNT; + /* FMA4, an AMD encoding that no Zen part implements, under the + * same OS state as FMA3. */ + if (ymm_state && (flags[2] & (1 << 16))) + cpu |= RETRO_SIMD_FMA4; + } +#elif defined(__linux__) + if (check_arm_cpu_feature("neon")) + { + cpu |= RETRO_SIMD_NEON; +#ifdef CPU_ARM_RUNFAST + arm_enable_runfast_mode(); +#endif + } + + if (check_arm_cpu_feature("vfpv3")) + cpu |= RETRO_SIMD_VFPV3; + + if (check_arm_cpu_feature("vfpv4")) + cpu |= RETRO_SIMD_VFPV4; + + /* Part of the optional Cryptographic Extension, which an + * implementation may leave out or hold in reset, so a 64-bit ARM + * CPU does not imply it. */ + if (check_arm_cpu_feature("aes")) + cpu |= RETRO_SIMD_AES; + + /* aarch64 lists it as "crc32" in the Features: line. */ + if (check_arm_cpu_feature("crc32")) + cpu |= RETRO_SIMD_CRC32; + + if (check_arm_cpu_feature("sha512")) + cpu |= RETRO_SIMD_SHA512; + + if (check_arm_cpu_feature("sha1")) + cpu |= RETRO_SIMD_SHA1; + + if (check_arm_cpu_feature("sha2")) + cpu |= RETRO_SIMD_SHA256; + + if (check_arm_cpu_feature("asimd")) + { + cpu |= RETRO_SIMD_ASIMD; + + /* ASIMD *is* NEON: Advanced SIMD is what aarch64 kernels call + * it in the Features: line, where 32-bit ARM says "neon". It + * is architecturally mandatory in ARMv8-A, so a CPU reporting + * asimd has NEON by definition and callers testing + * RETRO_SIMD_NEON must see it. + * + * This used to be gated on __ARM_NEON__, which is the LEGACY + * 32-bit spelling. aarch64 toolchains define __ARM_NEON (no + * trailing underscores) instead - verified against the NDK's + * own compiler, which reports __ARM_NEON 1 and no __ARM_NEON__ + * for aarch64-linux-android - so the guard was false on every + * 64-bit build and NEON was never reported there. That is why + * System Information listed ASIMD alone on a device whose CPU + * has had NEON since it was designed. */ + cpu |= RETRO_SIMD_NEON; + +#ifdef CPU_ARM_RUNFAST + arm_enable_runfast_mode(); +#endif + } +#elif defined(__ARM_NEON) || defined(__ARM_NEON__) + cpu |= RETRO_SIMD_NEON; +#ifdef CPU_ARM_RUNFAST + arm_enable_runfast_mode(); +#endif +#elif defined(__ALTIVEC__) + cpu |= RETRO_SIMD_VMX; +#elif defined(XBOX360) + cpu |= RETRO_SIMD_VMX128; +#elif defined(PSP) || defined(PS2) + cpu |= RETRO_SIMD_VFPU; +#elif defined(GEKKO) + cpu |= RETRO_SIMD_PS; +#endif + + return cpu; +} + +/* The probe is not cheap: on x86 it issues a chain of serialising CPUID + * instructions, and on ARM/Linux it opens and line-parses /proc/cpuinfo + * once per feature queried. The answer cannot change over the lifetime + * of the process, so probe once and hand out the cached mask thereafter. + * Callers that sit on a per-frame or per-asset path (the image decoders + * select their SIMD kernels this way) were paying the full probe on every + * single call. */ +uint64_t cpu_features_get(void) +{ + /* The mask is published with a release store and consumed with an + * acquire load, so a thread that observes the ready flag is + * guaranteed to see the fully written mask. + * + * The probe being idempotent is not on its own enough: if several + * threads reach it before any has published, they all write + * cpu_features_cache with plain stores, and those writes are + * unordered with respect to one another. Same value or not, that + * is a data race and ThreadSanitizer reports it. Elect exactly one + * writer with an atomic increment; the threads that lose return the + * value they computed themselves, which is identical, so nobody + * spins and nobody writes memory another thread is writing. */ + static uint64_t cpu_features_cache; + static retro_atomic_int_t cpu_features_claim; /* 0 = unclaimed */ + static retro_atomic_int_t cpu_features_ready; /* 0 = not probed yet */ + uint64_t cpu; + + if (retro_atomic_load_acquire_int(&cpu_features_ready)) + return cpu_features_cache; + + cpu = cpu_features_probe(); + + if (retro_atomic_fetch_add_int(&cpu_features_claim, 1) == 0) + { + cpu_features_cache = cpu; + retro_atomic_store_release_int(&cpu_features_ready, 1); + } + + return cpu; +} + +void cpu_features_get_model_name(char *s, int len) +{ +#if defined(CPU_X86) && !defined(__MACH__) + union { + int32_t i[4]; + uint32_t u[4]; + uint8_t s[16]; + } flags; + int i, j; + int pos = 0; + bool start = false; + + if (!s) + return; + + x86_cpuid(0x80000000, flags.i); + + /* Check for additional cpuid attributes availability */ + if (flags.u[0] < 0x80000004) + return; + + for (i = 0; i < 3; i++) + { + memset(flags.i, 0, sizeof(flags.i)); + x86_cpuid(0x80000002 + i, flags.i); + + for (j = 0; j < (int)sizeof(flags.s); j++) + { + if (!start && flags.s[j] == ' ') + continue; + + start = true; + + if (pos == len - 1) + { + /* truncate if we ran out of room */ + s[pos] = '\0'; + goto end; + } + + s[pos++] = flags.s[j]; + } + } +end: + /* terminate our string */ + if (pos < len) + s[pos] = '\0'; +#elif defined(__MACH__) + if (!s) + return; + { + size_t __len = len; + sysctlbyname("machdep.cpu.brand_string", s, &__len, NULL, 0); + } +#elif defined(__linux__) + if (!s) + return; + { + char *model_name, line[128]; + FILE *fp = fopen("/proc/cpuinfo", "r"); + + if (!fp) + return; + + while (fgets(line, sizeof(line), fp)) + { + if (strncmp(line, "model name", 10)) + continue; + + if ((model_name = strstr(line + 10, ": "))) + { + model_name += 2; + strlcpy(s, model_name, len); + } + + break; + } + + fclose(fp); + +#if defined(WEBOS) + struct stat st; + if (stat("/usr/bin/lscpu", &st) == 0) + { + FILE *pipe = popen("/usr/bin/lscpu", "r"); + if (pipe) + { + char buf[256]; + + while (fgets(buf, sizeof(buf), pipe)) + { + if (strncmp(buf, "Model name:", 11) == 0) + { + const char *p = strchr(buf, ':'); + if (p) + { + size_t len2; + p++; /* skip ':' */ + while (*p == ' ' || *p == '\t') + p++; + len2 = strcspn(p, "\r\n"); + + if (len2 > 0) + { + char *tmp = malloc(len2 + 1); + if (tmp) + { + memcpy(tmp, p, len2); + tmp[len2] = '\0'; + + if (s[0] != '\0') + { + size_t oldlen = strlen(s); + char *combined = (char*)malloc(oldlen + len2 + 4); + if (combined) + { + memcpy(combined, s, oldlen); + combined[oldlen] = ' '; + combined[oldlen + 1] = '('; + memcpy(combined + oldlen + 2, tmp, len2); + combined[oldlen + 2 + len2] = ')'; + combined[oldlen + 2 + len2 + 1] = '\0'; + + strlcpy(s, combined, len); + free(combined); + } + } + else + strlcpy(s, tmp, len); + free(tmp); + } + } + } + break; + } + } + pclose(pipe); + } + } +#endif + } +#endif +} + +/* Sleep until cpu_features_get_time_usec() reads at least @deadline. + * It lives here, beside the clock it is measured on, and nowhere a + * launcher compiles: the salamanders build rtime.c for localtime alone + * and never link this file. + * + * Absolute where the platform offers it, so the time between reading + * the clock and entering the kernel, and any early or interrupted + * wake, are not added to when the caller comes back. Never early + * against that clock on any backend; late as every sleep may be, and + * a caller that needs the instant itself sleeps short and spins the + * rest, as the frame limiter does with its measured margin. */ +#if (defined(__linux__) || defined(ANDROID)) && !defined(__MACH__) +/* The exact tool: the clock above is clock_gettime(CLOCK_MONOTONIC) + * here, and clock_nanosleep() takes an absolute deadline on the same + * clock. EINTR re-arms against the unchanged deadline by definition + * of TIMER_ABSTIME. */ +void retro_sleep_until_us(retro_time_t deadline) +{ + struct timespec ts; + ts.tv_sec = (time_t)(deadline / 1000000); + ts.tv_nsec = (long)((deadline % 1000000) * 1000); + while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0) + ; +} +#elif defined(__APPLE__) && defined(__MACH__) +#include +/* mach_wait_until() is the absolute wait, on the Mach clock; the + * public microsecond clock above is CLOCK_MONOTONIC, which Darwin + * derives from the same hardware ticks. The two are bridged with one + * paired read - nanoseconds apart - and unlike a relative sleep the + * arming is still absolute in Mach terms, so an early wake or the + * kernel's leeway re-arms nothing and accumulates nothing. */ +void retro_sleep_until_us(retro_time_t deadline) +{ + static mach_timebase_info_data_t tb; + uint64_t ticks; + retro_time_t now = cpu_features_get_time_usec(); + uint64_t mach_now = mach_absolute_time(); + if (deadline <= now) + return; + /* A constant; a racing first read fills in the same values. */ + if (!tb.denom) + mach_timebase_info(&tb); + ticks = (uint64_t)(deadline - now) * 1000 * tb.denom / tb.numer; + mach_wait_until(mach_now + ticks); +} +#else +/* Windows and everything else: re-arm the platform's best relative + * wait - retro_sleep_us from retro_timers.h, included above on every + * platform - against the deadline until the clock agrees. On desktop + * Windows that wait is rtime.c's per-thread high-resolution timer, so + * each lap is microsecond-grained; on a platform whose sleep rounds + * up the loop ends one lap past the deadline, no worse than the + * relative call was. */ +void retro_sleep_until_us(retro_time_t deadline) +{ + for (;;) + { + retro_time_t now = cpu_features_get_time_usec(); + if (now >= deadline) + return; + retro_sleep_us((unsigned)(deadline - now)); + } +} +#endif diff --git a/dependencies/libretro-common/file/file_path.c b/dependencies/libretro-common/file/file_path.c new file mode 100644 index 00000000..5c48ea92 --- /dev/null +++ b/dependencies/libretro-common/file/file_path.c @@ -0,0 +1,1594 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (file_path.c). + * --------------------------------------------------------------------------------------- + * + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include