diff --git a/.appveyor.yml b/.appveyor.yml
index 44a5dbc..db3c3a0 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,34 +1,51 @@
-image: 'Visual Studio 2017'
+image:
+ - macOS
+ - 'Visual Studio 2019'
branches:
only:
- master
+ - develop
- /^v\d.*$/
- /^deploy-test(-.*)?$/
configuration:
- Release
-install:
- - git submodule update --init --recursive
+for:
+ - matrix:
+ only:
+ - image: 'Visual Studio 2019'
-build_script:
- - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
- - C:\msys64\usr\bin\bash -lc "cd \"%APPVEYOR_BUILD_FOLDER%\" && scripts/appveyor.sh"
+ install:
+ - git submodule update --init --recursive
-artifacts:
- - path: Chiaki
- name: Chiaki
- - path: Chiaki-PDB
- name: Chiaki-PDB
+ build_script:
+ - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
+ - C:\msys64\usr\bin\bash -lc "cd \"%APPVEYOR_BUILD_FOLDER%\" && scripts/appveyor-win.sh"
-deploy:
- description: 'Chiaki Binaries'
- provider: GitHub
- draft: true
- auth_token:
- secure: Amvzm3PMM5nv+iFsqaU7TZ9fgyYt/YIrOLV0QMiCyOoUlLRIaiYxWiJ7maTpxhZ9
- artifact: "Chiaki"
- on:
- appveyor_repo_tag: true
+ artifacts:
+ - path: Chiaki
+ name: Chiaki
+ - path: Chiaki-PDB
+ name: Chiaki-PDB
+ - matrix:
+ only:
+ - image: macOS
+
+ install:
+ - git submodule update --init --recursive
+ - sudo pip3 install protobuf
+ - HOMEBREW_NO_AUTO_UPDATE=1 brew install qt@5 opus openssl@1.1 nasm sdl2 protobuf
+ - scripts/build-ffmpeg.sh
+
+ build_script:
+ - export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt@5"
+ - scripts/build-common.sh
+ - cp -a build/gui/chiaki.app Chiaki.app
+ - /usr/local/opt/qt@5/bin/macdeployqt Chiaki.app -dmg
+
+ artifacts:
+ - path: Chiaki.dmg
+ name: Chiaki
diff --git a/.builds/alpine.yml b/.builds/alpine.yml
deleted file mode 100644
index 88cb3ad..0000000
--- a/.builds/alpine.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-image: alpine/edge
-
-sources:
- - https://github.com/thestr4ng3r/chiaki.git
-
-packages:
- - cmake
- - protoc
- - py3-protobuf
- - opus-dev
- - qt5-qtbase-dev
- - qt5-qtsvg-dev
- - qt5-qtmultimedia-dev
- - ffmpeg-dev
- - sdl2-dev
-
-tasks:
- - build: |
- cd chiaki
- mkdir build && cd build
- cmake ..
- make -j4
- test/chiaki-unit
-
diff --git a/.builds/android.yml b/.builds/android.yml
new file mode 100644
index 0000000..20cd88b
--- /dev/null
+++ b/.builds/android.yml
@@ -0,0 +1,28 @@
+image: alpine/latest
+
+packages:
+- docker
+
+sources:
+- https://git.sr.ht/~thestr4ng3r/chiaki
+
+artifacts:
+- Chiaki.apk
+- Chiaki.aab
+
+secrets:
+- 163950ff-2ac4-423d-a280-d2d9edbef000
+- f4bce41f-f96b-4633-80d8-0ff5dd74dc2a
+
+tasks:
+- build: |
+ cp -v ~/chiaki-local.properties chiaki/android/local.properties || echo "Secrets not available"
+ sudo service docker start
+ timeout 15 sh -c "until sudo docker info; do sleep 0.5; done"
+ sudo docker run \
+ -v /home/build:/home/build \
+ -u $(id -u):$(id -g) \
+ thestr4ng3r/android:90d826e \
+ /bin/bash -c "cd /home/build/chiaki/android && ./gradlew assembleRelease bundleRelease"
+ cp chiaki/android/app/build/outputs/apk/release/app-release*.apk Chiaki.apk
+ cp chiaki/android/app/build/outputs/bundle/release/app-release*.aab Chiaki.aab
diff --git a/.builds/common.yml b/.builds/common.yml
new file mode 100644
index 0000000..b692b08
--- /dev/null
+++ b/.builds/common.yml
@@ -0,0 +1,55 @@
+
+image: alpine/latest
+
+sources:
+ - https://git.sr.ht/~thestr4ng3r/chiaki
+
+packages:
+ - cmake
+ - ninja
+ - protoc
+ - py3-protobuf
+ - py3-setuptools
+ - opus-dev
+ - qt5-qtbase-dev
+ - qt5-qtsvg-dev
+ - qt5-qtmultimedia-dev
+ - ffmpeg-dev
+ - sdl2-dev
+ - podman
+ - fuse
+ - udev
+ - argp-standalone
+
+artifacts:
+ - chiaki.nro
+ - Chiaki.AppImage
+
+tasks:
+ - setup_podman: |
+ sudo rc-service udev start
+ sudo rc-service cgroups start
+ sudo rc-service fuse start # Fuse for AppImages
+ echo build:100000:65536 | sudo tee /etc/subuid
+ echo build:100000:65536 | sudo tee /etc/subgid
+ # https://www.kernel.org/doc/Documentation/networking/tuntap.txt
+ # for slirp4netns
+ sudo mkdir -p /dev/net
+ sudo mknod /dev/net/tun c 10 200
+ sudo chmod 0666 /dev/net/tun
+ - local_build_and_test: |
+ cd chiaki
+ cmake -Bbuild -GNinja -DCHIAKI_ENABLE_CLI=ON -DCHIAKI_ENABLE_GUI=ON -DCHIAKI_CLI_ARGP_STANDALONE=ON
+ ninja -C build
+ build/test/chiaki-unit
+ - appimage: |
+ cd chiaki
+ scripts/run-podman-build-appimage.sh
+ cp appimage/Chiaki.AppImage ../Chiaki.AppImage
+ - switch: |
+ cd chiaki
+ scripts/switch/run-podman-build-chiaki.sh
+ cp build_switch/switch/chiaki.nro ../chiaki.nro
+ - bullseye: |
+ cd chiaki
+ scripts/run-podman-build-bullseye.sh
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index efbc9c9..72109b5 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -1,19 +1,21 @@
-image: freebsd/latest
+image: freebsd/14.x
sources:
- - https://github.com/thestr4ng3r/chiaki
+ - https://git.sr.ht/~thestr4ng3r/chiaki
packages:
- cmake
- protobuf
- - py36-protobuf
+ - py311-setuptools # should not be needed with nanopb >= 0.4.9
+ - py311-protobuf
- opus
- qt5-core
- qt5-qmake
- qt5-buildtools
- qt5-gui
- qt5-widgets
+ - qt5-concurrent
- qt5-svg
- qt5-opengl
- qt5-multimedia
diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml
new file mode 100644
index 0000000..e1595ac
--- /dev/null
+++ b/.builds/openbsd.yml
@@ -0,0 +1,26 @@
+
+image: openbsd/latest
+
+sources:
+ - https://git.sr.ht/~thestr4ng3r/chiaki
+
+packages:
+ - cmake
+ - protobuf
+ - py3-setuptools # should not be needed with nanopb >= 0.4.9
+ - py3-protobuf
+ - opus
+ - qtbase
+ - qtsvg
+ - qtmultimedia
+ - ffmpeg
+ - sdl2
+
+tasks:
+ - build: |
+ cd chiaki
+ mkdir build && cd build
+ cmake -DCMAKE_PREFIX_PATH="/usr/local/lib;/usr/local/lib/qt5/cmake" ..
+ make -j4
+ test/chiaki-unit
+
diff --git a/.gitignore b/.gitignore
index 43359e6..f30ef42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
.idea
build
cmake-build-*
+/build_*
+/build-*
.DS_store
*.AppImage
appdir
@@ -20,3 +22,12 @@ chiaki.rb
*.jks
secret.tar
keystore-env.sh
+compile_commands.json
+.ccls-cache
+.cache/
+.gdb_history
+chiaki.conf
+/appimage
+.cache/
+/*.app
+/*.dmg
diff --git a/.gitmodules b/.gitmodules
index 1d25e40..250f8fe 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -6,10 +6,13 @@
url = https://github.com/nanopb/nanopb.git
[submodule "third-party/jerasure"]
path = third-party/jerasure
- url = https://github.com/thestr4ng3r/jerasure.git
+ url = https://git.sr.ht/~thestr4ng3r/jerasure
[submodule "third-party/gf-complete"]
path = third-party/gf-complete
- url = https://github.com/thestr4ng3r/gf-complete.git
+ url = https://git.sr.ht/~thestr4ng3r/gf-complete
[submodule "android/app/src/main/cpp/oboe"]
path = android/app/src/main/cpp/oboe
url = https://github.com/google/oboe
+[submodule "switch/borealis"]
+ path = switch/borealis
+ url = https://git.sr.ht/~thestr4ng3r/borealis
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 11f706c..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,159 +0,0 @@
-
-language: cpp
-
-branches:
- only:
- - master
- - /^v\d.*$/
- - /^deploy-test(-.*)?$/
-
-before_script:
- - export CHIAKI_VERSION="$TRAVIS_TAG"
- - if [ -z "$CHIAKI_VERSION" ]; then export CHIAKI_VERSION="$TRAVIS_COMMIT"; fi
-
-matrix:
- include:
- - name: Linux (Bionic)
- os: linux
- dist: bionic
- addons:
- apt:
- sources:
- - sourceline: "ppa:beineri/opt-qt-5.12.0-bionic"
- packages:
- - protobuf-compiler
- - python3-protobuf
- - libopus-dev
- - qt512base
- - qt512multimedia
- - qt512gamepad
- - qt512svg
- - libgl1-mesa-dev
- - nasm
- - libsdl2-dev
- env:
- - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;/opt/qt512"
- - CMAKE_EXTRA_ARGS="-DCMAKE_INSTALL_PREFIX=/usr"
- - DEPLOY=0
- install:
- - scripts/build-ffmpeg.sh
- script:
- - scripts/travis-build.sh || exit 1
- - source scripts/travis-appimage.sh
-
- - name: Linux (Xenial, Deploy)
- os: linux
- dist: xenial
- addons:
- apt:
- sources:
- - sourceline: "ppa:beineri/opt-qt-5.12.3-xenial"
- packages:
- - python3-pip
- - libopus-dev
- - qt512base
- - qt512multimedia
- - qt512svg
- - libgl1-mesa-dev
- - nasm
- env:
- - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;$TRAVIS_BUILD_DIR/sdl2-prefix;/opt/qt512"
- - CMAKE_EXTRA_ARGS="-DCMAKE_INSTALL_PREFIX=/usr"
- - SDL2_FROM_SRC=1
- - DEPLOY=1
- install:
- - sudo pip3 install protobuf
- - scripts/fetch-protoc.sh
- - export PATH="$TRAVIS_BUILD_DIR/protoc/bin:$PATH"
- - scripts/build-ffmpeg.sh
- - scripts/build-sdl2.sh
- script:
- - scripts/travis-build.sh || exit 1
- - source scripts/travis-appimage.sh
-
- - name: macOS
- os: osx
- osx_image: xcode11
- addons:
- homebrew:
- packages:
- - qt
- - opus
- - openssl@1.1
- - nasm
- - sdl2
- env:
- - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt"
- - CMAKE_EXTRA_ARGS=""
- - DEPLOY=1
- install:
- - scripts/build-ffmpeg.sh
- script:
- - scripts/travis-build.sh
- - cp -a build/gui/chiaki.app Chiaki.app
- - /usr/local/opt/qt/bin/macdeployqt Chiaki.app -dmg
- - export DEPLOY_FILE="Chiaki-${CHIAKI_VERSION}-macOS-x86_64.dmg"
- - mv Chiaki.dmg "$DEPLOY_FILE"
- - cmake -DCHIAKI_VERSION="${CHIAKI_VERSION}" -DCHIAKI_DMG="${DEPLOY_FILE}" -DCHIAKI_CASK_OUT=chiaki.rb -P scripts/configure-cask.cmake
- - echo "------------------- chiaki.rb cask -------------------"
- - cat chiaki.rb
- - echo "------------------------------------------------------"
-
- - name: Android
- language: android
- os: linux
- dist: trusty
- env:
- - DEPLOY=1
- android:
- components:
- - build-tools-29.0.2
- - android-29
- addons:
- apt:
- packages:
- - python3-pip
- install:
- - echo y | sdkmanager "ndk-bundle"
- - echo y | sdkmanager "cmake;3.10.2.4988404"
- - sudo pip3 install protobuf
- - scripts/fetch-protoc.sh
- - export PATH="$TRAVIS_BUILD_DIR/protoc/bin:$PATH"
- script:
- - cd android
- - ./gradlew assembleRelease bundleRelease
- - export DEPLOY_FILE_BASE="Chiaki-$CHIAKI_VERSION-Android"
- - export DEPLOY_FILE="$DEPLOY_FILE_BASE.a[pa][kb]"
- - cp app/build/outputs/apk/release/app-release.apk "../$DEPLOY_FILE_BASE.apk"
- - cp app/build/outputs/bundle/release/app-release.aab "../$DEPLOY_FILE_BASE.aab"
- - cd ..
-
- - name: "Source Package"
- os: linux
- dist: bionic
- env:
- - DEPLOY=1
- install: ~
- script:
- - find . -name ".git*" | xargs rm -rfv
- - mkdir chiaki && shopt -s extglob && mv !(chiaki) chiaki
- - export DEPLOY_FILE="chiaki-$CHIAKI_VERSION-src.tar.gz"
- - tar -czvf "$DEPLOY_FILE" chiaki
-
-before_install:
- - if [ ! -z "$encrypted_31d5e6477a29_iv" ]; then openssl aes-256-cbc -K $encrypted_31d5e6477a29_key -iv $encrypted_31d5e6477a29_iv -in secret.tar.enc -out secret.tar -d && tar -xf secret.tar; fi
-
-after_success:
- - if [ ! -z "$DEPLOY_FILE" ]; then curl -m 30 --upload-file "$DEPLOY_FILE" "https://transfer.sh/$DEPLOY_FILE"; echo; fi
-
-deploy:
- skip_cleanup: true
- provider: releases
- draft: true
- api_key:
- secure: R7RjLOuGFda05EJeNX2lNG135xKU2w9IQn7p1H1P2zw4zlQMgSBpNRaW8hE408x5KJUjptJTF6QaYYmPWbHlf9VEPFVIcVzSp8YSd2Cdr+GKhmFgWF+fJPBj5y9NNqohwxvK3Nrugh0v6yVQiEYEGF7WArU6dvymSNNTw/EqXtfrOvwUgSf1bDAzQAsXn3E6Ptzf9DrQU8+mOgMSqT/3Wy5207KLmWTtwBWDgkskKwS9OEXk3tDd6U4uT7NFHHmcw+ZjQXRD+yHSHUWYs1oKR4IfgPFxQfEK0Txhkxdf3yj1aNweuk7GGC3cfRaarUfRQpoYqYYCxhTfGZ2b4rVgX3XpssMY7ZmSZHRi/SX08ETXF/c7PZGzr0RPFXZLgAGjgN6O2Dbb9agc3tOUGDUuqKEWX9sALm82WS0FRAFrFLENgMFsj5hu+DKIIkAU2yEsadYKjjhC+q+mTAEkxKKknvM50Xpx3tE1TlP/31Z53v4/NydHIHXPJ72V3mnuoTacwhG2SkGtjMbLCnEZDCtu9C4556oa7Z29cqafv90ZD7lTQMV+ijKvjxgOC9u1GeemmZLofRGDFyYSqKxOpYxxxXGOhs+7FMAdKP00h++MTLwRwIebKQs0fW0XiNKmwushWOUU8sXI1jxTbwe9dPQsspxHRv/mVo6l2vUcBjC19K0=
- file_glob: true
- file: $DEPLOY_FILE
- on:
- tags: true
- condition: $DEPLOY = 1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4e5926e..64caaee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,22 +3,82 @@ cmake_minimum_required(VERSION 3.2)
project(chiaki)
+# Like option(), but the value can also be AUTO
+macro(tri_option name desc default)
+ set("${name}" "${default}" CACHE STRING "${desc}")
+ set_property(CACHE "${name}" PROPERTY STRINGS AUTO ON OFF)
+endmacro()
+
option(CHIAKI_ENABLE_TESTS "Enable tests for Chiaki" ON)
option(CHIAKI_ENABLE_CLI "Enable CLI for Chiaki" OFF)
option(CHIAKI_ENABLE_GUI "Enable Qt GUI" ON)
option(CHIAKI_ENABLE_ANDROID "Enable Android (Use only as part of the Gradle Project)" OFF)
+option(CHIAKI_ENABLE_BOREALIS "Enable Borealis GUI (For Nintendo Switch or PC)" OFF)
+tri_option(CHIAKI_ENABLE_SETSU "Enable libsetsu for touchpad input from controller" AUTO)
option(CHIAKI_LIB_ENABLE_OPUS "Use Opus as part of Chiaki Lib" ON)
+if(CHIAKI_ENABLE_GUI OR CHIAKI_ENABLE_BOREALIS)
+ set(CHIAKI_FFMPEG_DEFAULT ON)
+else()
+ set(CHIAKI_FFMPEG_DEFAULT AUTO)
+endif()
+tri_option(CHIAKI_ENABLE_FFMPEG_DECODER "Enable FFMPEG video decoder" ${CHIAKI_FFMPEG_DEFAULT})
+tri_option(CHIAKI_ENABLE_PI_DECODER "Enable Raspberry Pi-specific video decoder (requires libraspberrypi0 and libraspberrypi-doc)" AUTO)
+option(CHIAKI_LIB_ENABLE_MBEDTLS "Use mbedtls instead of OpenSSL as part of Chiaki Lib" OFF)
+option(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT "Fetch Mbed TLS instead of using system-provided libs" OFF)
option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF)
-option(CHIAKI_GUI_ENABLE_QT_GAMEPAD "Use QtGamepad for Input" OFF)
option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON)
option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF)
+tri_option(CHIAKI_USE_SYSTEM_JERASURE "Use system-provided jerasure instead of submodule" AUTO)
+tri_option(CHIAKI_USE_SYSTEM_NANOPB "Use system-provided nanopb instead of submodule" AUTO)
-set(CHIAKI_VERSION_MAJOR 1)
-set(CHIAKI_VERSION_MINOR 1)
-set(CHIAKI_VERSION_PATCH 1)
+set(CHIAKI_VERSION_MAJOR 2)
+set(CHIAKI_VERSION_MINOR 2)
+set(CHIAKI_VERSION_PATCH 0)
set(CHIAKI_VERSION ${CHIAKI_VERSION_MAJOR}.${CHIAKI_VERSION_MINOR}.${CHIAKI_VERSION_PATCH})
-list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+set(CPACK_PACKAGE_NAME "chiaki")
+set(CPACK_PACKAGE_DESCRIPTION "Open Source PS4 remote play client")
+set(CPACK_PACKAGE_VERSION_MAJOR ${CHIAKI_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${CHIAKI_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${CHIAKI_VERSION_PATCH})
+set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
+set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION})
+set(CPACK_DEBIAN_PACKAGE_SECTION "games")
+include(CPack)
+
+set(CHIAKI_IS_SWITCH ${NSWITCH})
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_CURRENT_SOURCE_DIR}/setsu/cmake")
+
+set(CMAKE_CXX_STANDARD 11)
+
+if(CHIAKI_IS_SWITCH)
+ # force mbedtls as crypto lib
+ set(CHIAKI_LIB_ENABLE_MBEDTLS ON)
+ add_definitions(-D__SWITCH__)
+endif()
+
+if(CHIAKI_USE_SYSTEM_JERASURE)
+ if(CHIAKI_USE_SYSTEM_JERASURE STREQUAL AUTO)
+ find_package(Jerasure QUIET)
+ set(CHIAKI_USE_SYSTEM_JERASURE ${Jerasure_FOUND})
+ else()
+ find_package(Jerasure REQUIRED)
+ set(CHIAKI_USE_SYSTEM_JERASURE ON)
+ endif()
+endif()
+
+find_package(PythonInterp 3 REQUIRED) # Make sure nanopb doesn't find Python 2.7 because Python 2 should just die.
+
+if(CHIAKI_USE_SYSTEM_NANOPB)
+ if(CHIAKI_USE_SYSTEM_NANOPB STREQUAL AUTO)
+ find_package(Nanopb QUIET)
+ set(CHIAKI_USE_SYSTEM_NANOPB ${Nanopb_FOUND})
+ else()
+ find_package(Nanopb REQUIRED)
+ set(CHIAKI_USE_SYSTEM_NANOPB ON)
+ endif()
+endif()
add_subdirectory(third-party)
@@ -28,12 +88,101 @@ if(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT)
include(OpenSSLExternalProject)
endif()
+if(CHIAKI_LIB_ENABLE_MBEDTLS)
+ add_definitions(-DCHIAKI_LIB_ENABLE_MBEDTLS)
+ if(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT)
+ set(FETCHCONTENT_QUIET CACHE BOOL FALSE)
+ include(FetchContent)
+ set(ENABLE_TESTING CACHE INTERNAL OFF)
+ set(ENABLE_PROGRAMS CACHE INTERNAL OFF)
+ set(USE_SHARED_MBEDTLS_LIBRARY CACHE INTERNAL OFF)
+ FetchContent_Declare(
+ mbedtls
+ GIT_REPOSITORY https://github.com/Mbed-TLS/mbedtls.git
+ GIT_TAG 8b3f26a5ac38d4fdccbc5c5366229f3e01dafcc0 # v2.28.0
+ GIT_PROGRESS TRUE
+ )
+ FetchContent_MakeAvailable(mbedtls)
+ endif()
+endif()
+
+if(CHIAKI_ENABLE_FFMPEG_DECODER)
+ find_package(FFMPEG COMPONENTS avcodec avutil)
+ if(FFMPEG_FOUND)
+ set(CHIAKI_ENABLE_FFMPEG_DECODER ON)
+ else()
+ if(NOT CHIAKI_ENABLE_FFMPEG_DECODER STREQUAL AUTO)
+ message(FATAL_ERROR "CHIAKI_ENABLE_FFMPEG_DECODER is set to ON, but ffmpeg could not be found.")
+ endif()
+ set(CHIAKI_ENABLE_FFMPEG_DECODER OFF)
+ endif()
+endif()
+
+if(CHIAKI_ENABLE_FFMPEG_DECODER)
+ message(STATUS "FFMPEG Decoder enabled")
+else()
+ message(STATUS "FFMPEG Decoder disabled")
+endif()
+
+if(CHIAKI_ENABLE_PI_DECODER)
+ find_package(ILClient)
+ if(ILClient_FOUND)
+ set(CHIAKI_ENABLE_PI_DECODER ON)
+ else()
+ if(NOT CHIAKI_ENABLE_PI_DECODER STREQUAL AUTO)
+ message(FATAL_ERROR "
+CHIAKI_ENABLE_PI_DECODER is set to ON, but its dependencies (ilclient source and libs) could not be resolved.
+The Raspberry Pi Decoder is only supported on Raspberry Pi OS and requires libraspberrypi0 and libraspberrypi-doc.")
+ endif()
+ set(CHIAKI_ENABLE_PI_DECODER OFF)
+ endif()
+endif()
+
+if(CHIAKI_ENABLE_PI_DECODER)
+ message(STATUS "Pi Decoder enabled")
+else()
+ message(STATUS "Pi Decoder disabled")
+endif()
+
add_subdirectory(lib)
if(CHIAKI_ENABLE_CLI)
add_subdirectory(cli)
endif()
+if(CHIAKI_ENABLE_GUI AND CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
+ find_package(SDL2 MODULE REQUIRED)
+endif()
+
+if(CHIAKI_ENABLE_SETSU)
+ if(CHIAKI_ENABLE_SETSU STREQUAL AUTO AND SDL2_FOUND AND (SDL2_VERSION_MINOR GREATER 0 OR SDL2_VERSION_PATCH GREATER_EQUAL 14))
+ message(STATUS "SDL version ${SDL2_VERSION} is >= 2.0.14, disabling Setsu")
+ set(CHIAKI_ENABLE_SETSU OFF)
+ else()
+ find_package(Udev QUIET)
+ find_package(Evdev QUIET)
+ if(Udev_FOUND AND Evdev_FOUND)
+ set(CHIAKI_ENABLE_SETSU ON)
+ else()
+ if(NOT CHIAKI_ENABLE_SETSU STREQUAL AUTO)
+ message(FATAL_ERROR "
+ CHIAKI_ENABLE_SETSU is set to ON, but its dependencies (udev and evdev) could not be resolved.
+ Keep in mind that setsu is only supported on Linux!")
+ endif()
+ set(CHIAKI_ENABLE_SETSU OFF)
+ endif()
+ if(CHIAKI_ENABLE_SETSU)
+ add_subdirectory(setsu)
+ endif()
+ endif()
+endif()
+
+if(CHIAKI_ENABLE_SETSU)
+ message(STATUS "Setsu enabled")
+else()
+ message(STATUS "Setsu disabled")
+endif()
+
if(CHIAKI_ENABLE_GUI)
add_subdirectory(gui)
endif()
@@ -45,4 +194,8 @@ endif()
if(CHIAKI_ENABLE_ANDROID)
add_subdirectory(android/app)
-endif()
\ No newline at end of file
+endif()
+
+if(CHIAKI_ENABLE_BOREALIS)
+ add_subdirectory(switch)
+endif()
diff --git a/COPYING b/COPYING
index f288702..c3ef0cb 100644
--- a/COPYING
+++ b/COPYING
@@ -1,5 +1,5 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
@@ -7,17 +7,15 @@
Preamble
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
+our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
+software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
The precise terms and conditions for copying, distribution and
modification follow.
@@ -72,7 +60,7 @@ modification follow.
0. Definitions.
- "This License" refers to version 3 of the GNU General Public License.
+ "This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
- 13. Use with the GNU Affero General Public License.
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
+under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
+Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
+GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
+versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@@ -635,40 +633,40 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
+ it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
+ GNU Affero General Public License for more details.
- You should have received a copy of the GNU General Public License
+ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
+For more information on this, and how to apply and follow the GNU AGPL, see
.
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
+Additional permission under GNU AGPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.
diff --git a/LICENSES/AGPL-3.0-only-OpenSSL.txt b/LICENSES/AGPL-3.0-only-OpenSSL.txt
new file mode 100644
index 0000000..c3ef0cb
--- /dev/null
+++ b/LICENSES/AGPL-3.0-only-OpenSSL.txt
@@ -0,0 +1,672 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
+
+Additional permission under GNU AGPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.
diff --git a/README.md b/README.md
index 563a873..1a94b66 100644
--- a/README.md
+++ b/README.md
@@ -5,44 +5,43 @@
**Disclaimer:** This project is not endorsed or certified by Sony Interactive Entertainment LLC.
-[](https://travis-ci.com/thestr4ng3r/chiaki) [](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [](https://builds.sr.ht/~thestr4ng3r/chiaki?)
+[](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [](https://builds.sr.ht/~thestr4ng3r/chiaki?)
-Chiaki is a Free and Open Source Software Client for PlayStation 4 Remote Play
-for Linux, Android, macOS, Windows and potentially even more platforms.
+Chiaki is a Free and Open Source Software Client for PlayStation 4 and PlayStation 5 Remote Play
+for Linux, FreeBSD, OpenBSD, NetBSD, Android, macOS, Windows, Nintendo Switch and potentially even more platforms.
+
+## Project Status and Contributing
+
+As all relevant features are implemented, this project is considered to be finished and in maintenance mode only.
+No major updates are planned and contributions are only accepted in special cases such as security issues.
+The objective is to keep a stable base and not break existing support for less mainstream platforms such as BSDs.
+
+**For a more active, fast moving and community-oriented project, refer
+to [chiaki-ng](https://streetpea.github.io/chiaki-ng/) ("next generation").
+If you would like to contribute, this will likely also be the best place to do so.**

-## Features
-
-Everything necessary for a full streaming session, including the initial
-registration and wakeup of the console, is supported.
-The following features however are yet to be implemented:
-* Congestion Control
-* H264 Error Concealment (FEC and active error recovery however are implemented)
-* Touchpad support (Triggering the Touchpad Button is currently possible by pressing `T` on the keyboard)
-* Rumble
-* Configurable Keybindings
-
## Installing
-You can either download a pre-built release (easier) or build Chiaki from source.
+You can either download a pre-built release or build Chiaki from source.
### Downloading a Release
-Builds are provided for Linux, Android, macOS and Windows.
+Builds are provided for Linux, Android, macOS, Nintendo Switch and Windows.
-You can download them [here](https://github.com/thestr4ng3r/chiaki/releases).
+You can download them [here](https://git.sr.ht/~thestr4ng3r/chiaki/refs).
* **Linux**: The provided file is an [AppImage](https://appimage.org/). Simply make it executable (`chmod +x .AppImage`) and run it.
-* **Android**: Install from [Google Play](https://play.google.com/store/apps/details?id=com.metallic.chiaki) or download the APK from GitHub.
+* **Android**: Install from [F-Droid](https://f-droid.org/packages/com.metallic.chiaki/) or download the APK from Sourcehut.
* **macOS**: Drag the application from the `.dmg` into your Applications folder.
* **Windows**: Extract the `.zip` file and execute `chiaki.exe`.
+* **Switch**: Download the `.nro` file and copy it into the `switch/` directory on your SD card.
### Building from Source
-Dependencies are CMake, Qt 5 with QtMultimedia, QtOpenGL and QtSvg, FFMPEG (libavcodec with H264 is enough), libopus, OpenSSL 1.1,
-protoc and the protobuf Python library (only used during compilation for Nanopb).
-Then, Chiaki builds just like any other CMake project:
+Dependencies are CMake, Qt 5 with QtMultimedia, QtOpenGL and QtSvg, FFMPEG (libavcodec with H264 is enough), libopus, OpenSSL 1.1, SDL 2,
+protoc and the protobuf Python library (only used during compilation for Nanopb). Then, Chiaki builds just like any other CMake project:
```
git submodule update --init
mkdir build && cd build
@@ -50,13 +49,15 @@ cmake ..
make
```
+For more detailed platform-specific instructions, see [doc/platform-build.md](doc/platform-build.md) or [switch/](./switch/README.md) for Nintendo Switch.
+
## Usage
-If your PS4 is on your local network, is turned on or in standby mode and does not have Discovery explicitly disabled, Chiaki should find it.
+If your Console is on your local network, is turned on or in standby mode and does not have Discovery explicitly disabled, Chiaki should find it.
Otherwise, you can add it manually.
-To do so, click the "+" icon in the top right, and enter your PS4's IP address.
+To do so, click the "+" icon in the top right, and enter your Console's IP address.
-You will then need to register your PS4 with Chiaki. You will need two more pieces of information to do this.
+You will then need to register your Console with Chiaki. You will need two more pieces of information to do this.
### Obtaining your PSN AccountID
@@ -67,18 +68,18 @@ Simply run it in a terminal and follow the instructions. Once you know your ID,
### Obtaining a Registration PIN
-To register a PS4 with a PIN, it must be put into registration mode. To do this, on your PS4, simply go to:
-Settings -> Remote Play (ensure this is ticked) -> Add Device
+To register a Console with a PIN, it must be put into registration mode. To do this on a PS4, simply go to:
+Settings -> Remote Play -> Add Device, or on a PS5: Settings -> System -> Remote Play -> Link Device.
-You can now double-click your PS4 in Chiaki's main window to start Remote Play.
+You can now double-click your Console in Chiaki's main window to start Remote Play.
## Acknowledgements
This project has only been made possible because of the following Open Source projects:
-[radare2](https://github.com/radare/radare2),
-[Cutter](https://cutter.re/),
-[Frida](https://www.frida.re/) and
-[x64dbg](https://x64dbg.com/).
+[Rizin](https://rizin.re),
+[Cutter](https://cutter.re),
+[Frida](https://www.frida.re) and
+[x64dbg](https://x64dbg.com).
Also thanks to [delroth](https://github.com/delroth) for analyzing the registration and wakeup protocol,
[grill2010](https://github.com/grill2010) for analyzing the PSN's OAuth Login,
@@ -87,17 +88,27 @@ extremely helpful information about FEC and error correction.
## About
-Created by Florian Märkl.
+Created by Florian Märkl
This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+it under the terms of the GNU Affero General Public License version 3
+as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
+GNU Affero General Public License for more details.
-You should have received a copy of the GNU General Public License
+You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
+
+Additional permission under GNU AGPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6d7cbc4..21a89f3 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,6 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
def rootCMakeLists = "../../CMakeLists.txt"
@@ -18,28 +18,40 @@ def chiakiVersion = "$chiakiVersionMajor.$chiakiVersionMinor.$chiakiVersionPatch
println("Determined Chiaki Version: $chiakiVersion")
android {
- compileSdkVersion 29
- buildToolsVersion "29.0.2"
+ compileSdkVersion 33
defaultConfig {
applicationId "com.metallic.chiaki"
minSdkVersion 21
- targetSdkVersion 29
- versionCode 2
+ targetSdkVersion 33
+ versionCode 12
versionName chiakiVersion
externalNativeBuild {
cmake {
arguments "-DCHIAKI_ENABLE_TESTS=OFF",
"-DCHIAKI_ENABLE_CLI=OFF",
"-DCHIAKI_ENABLE_GUI=OFF",
+ "-DCHIAKI_ENABLE_SETSU=OFF",
"-DCHIAKI_ENABLE_ANDROID=ON",
+ "-DCHIAKI_ENABLE_FFMPEG_DECODER=OFF",
+ "-DCHIAKI_ENABLE_PI_DECODER=OFF",
"-DCHIAKI_LIB_ENABLE_OPUS=OFF",
"-DCHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT=ON"
}
}
}
+ buildFeatures {
+ viewBinding true
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
externalNativeBuild {
cmake {
- version "3.10.2+"
+ version "3.22.1"
path rootCMakeLists
}
}
@@ -53,21 +65,14 @@ android {
}
}
+
Properties properties = new Properties()
def propertiesFile = file("../local.properties")
if (propertiesFile.exists()) {
properties.load(propertiesFile.newDataInputStream())
}
- def keystoreFile = file("../../keystore.jks")
- if(System.getenv("TRAVIS") == "true" && keystoreFile.exists()) {
- println("Enabling Signing on Travis")
- buildTypes.release.signingConfig = signingConfigs.release
- signingConfigs.release.storeFile = keystoreFile
- signingConfigs.release.storePassword = System.getenv("android_keystore_pw")
- signingConfigs.release.keyAlias = "chiaki"
- signingConfigs.release.keyPassword = System.getenv("android_keystore_alias_pw")
- } else if(properties.containsKey("chiakiKeystore")) {
+ if(properties.containsKey("chiakiKeystore")) {
println("Enabling Local Signing")
buildTypes.release.signingConfig = signingConfigs.release
buildTypes.debug.signingConfig = signingConfigs.release
@@ -86,29 +91,26 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
}
}
-androidExtensions {
- // for @Parcelize
- experimental = true
-}
-
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.1.0'
- implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
- implementation 'androidx.recyclerview:recyclerview:1.0.0'
- implementation 'androidx.preference:preference:1.1.0'
- implementation 'com.google.android.material:material:1.1.0-beta01'
- implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
- implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.1.0'
- implementation "io.reactivex.rxjava2:rxjava:2.2.12"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.6.0'
+ implementation 'androidx.core:core-ktx:1.9.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
+ implementation 'androidx.preference:preference:1.2.0'
+ implementation 'com.google.android.material:material:1.8.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
+ implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.5.1'
+ implementation "io.reactivex.rxjava2:rxjava:2.2.20"
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
- def room_version = "2.2.1"
+ def room_version = "2.5.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-rxjava2:$room_version"
+ implementation "com.squareup.moshi:moshi:1.14.0"
+ kapt "com.squareup.moshi:moshi-kotlin-codegen:1.14.0"
}
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
index 8af7f58..ccbe180 100644
--- a/android/app/proguard-rules.pro
+++ b/android/app/proguard-rules.pro
@@ -21,4 +21,71 @@
#-renamesourcefileattribute SourceFile
-dontobfuscate
--keep class com.metallic.chiaki.** { *; }
\ No newline at end of file
+-keep class com.metallic.chiaki.** { *; }
+
+
+##########################################
+# Moshi
+##########################################
+
+# JSR 305 annotations are for embedding nullability information.
+-dontwarn javax.annotation.**
+
+-keepclasseswithmembers class * {
+ @com.squareup.moshi.* ;
+}
+
+-keep @com.squareup.moshi.JsonQualifier interface *
+
+# Enum field names are used by the integrated EnumJsonAdapter.
+# values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly
+# Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi.
+-keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum {
+ ;
+ **[] values();
+}
+
+# The name of @JsonClass types is used to look up the generated adapter.
+-keepnames @com.squareup.moshi.JsonClass class *
+
+# Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's
+# name. We will look this up reflectively to invoke the type's constructor.
+#
+# We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard
+# matching preceding parameters.
+-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
+-keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * {
+ synthetic (...);
+}
+
+# Retain generated JsonAdapters if annotated type is retained.
+-if @com.squareup.moshi.JsonClass class *
+-keep class <1>JsonAdapter {
+ (...);
+ ;
+}
+-if @com.squareup.moshi.JsonClass class **$*
+-keep class <1>_<2>JsonAdapter {
+ (...);
+ ;
+}
+-if @com.squareup.moshi.JsonClass class **$*$*
+-keep class <1>_<2>_<3>JsonAdapter {
+ (...);
+ ;
+}
+-if @com.squareup.moshi.JsonClass class **$*$*$*
+-keep class <1>_<2>_<3>_<4>JsonAdapter {
+ (...);
+ ;
+}
+-if @com.squareup.moshi.JsonClass class **$*$*$*$*
+-keep class <1>_<2>_<3>_<4>_<5>JsonAdapter {
+ (...);
+ ;
+}
+-if @com.squareup.moshi.JsonClass class **$*$*$*$*$*
+-keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter {
+ (...);
+ ;
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 14b0466..a0d28cb 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,8 @@
+
+
+ android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+ android:exported="true">
diff --git a/android/app/src/main/cpp/audio-decoder.c b/android/app/src/main/cpp/audio-decoder.c
index f7a979d..35f97e9 100644
--- a/android/app/src/main/cpp/audio-decoder.c
+++ b/android/app/src/main/cpp/audio-decoder.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include "audio-decoder.h"
diff --git a/android/app/src/main/cpp/audio-decoder.h b/android/app/src/main/cpp/audio-decoder.h
index c237b4b..c365cd2 100644
--- a/android/app/src/main/cpp/audio-decoder.h
+++ b/android/app/src/main/cpp/audio-decoder.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_JNI_AUDIO_DECODER_H
#define CHIAKI_JNI_AUDIO_DECODER_H
diff --git a/android/app/src/main/cpp/audio-output.cpp b/android/app/src/main/cpp/audio-output.cpp
index 4531830..53157f1 100644
--- a/android/app/src/main/cpp/audio-output.cpp
+++ b/android/app/src/main/cpp/audio-output.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include "audio-output.h"
diff --git a/android/app/src/main/cpp/audio-output.h b/android/app/src/main/cpp/audio-output.h
index 3af3002..b521ebe 100644
--- a/android/app/src/main/cpp/audio-output.h
+++ b/android/app/src/main/cpp/audio-output.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_JNI_AUDIO_OUTPUT_H
#define CHIAKI_JNI_AUDIO_OUTPUT_H
diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c
index e0884ce..9b14b60 100644
--- a/android/app/src/main/cpp/chiaki-jni.c
+++ b/android/app/src/main/cpp/chiaki-jni.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -125,18 +110,18 @@ JNIEXPORT jstring JNICALL JNI_FCN(quitReasonToString)(JNIEnv *env, jobject obj,
return E->NewStringUTF(env, chiaki_quit_reason_string((ChiakiQuitReason)value));
}
-JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsStopped)(JNIEnv *env, jobject obj, jint value)
+JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsError)(JNIEnv *env, jobject obj, jint value)
{
- return value == CHIAKI_QUIT_REASON_STOPPED;
+ return chiaki_quit_reason_is_error(value);
}
-JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset)
+JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset, jobject codec)
{
ChiakiConnectVideoProfile profile = { 0 };
chiaki_connect_video_profile_preset(&profile, (ChiakiVideoResolutionPreset)resolution_preset, (ChiakiVideoFPSPreset)fps_preset);
jclass profile_class = E->FindClass(env, BASE_PACKAGE"/ConnectVideoProfile");
- jmethodID profile_ctor = E->GetMethodID(env, profile_class, "", "(IIII)V");
- return E->NewObject(env, profile_class, profile_ctor, profile.width, profile.height, profile.max_fps, profile.bitrate);
+ jmethodID profile_ctor = E->GetMethodID(env, profile_class, "", "(IIIIL"BASE_PACKAGE"/Codec;)V");
+ return E->NewObject(env, profile_class, profile_ctor, profile.width, profile.height, profile.max_fps, profile.bitrate, codec);
}
typedef struct android_chiaki_session_t
@@ -148,6 +133,7 @@ typedef struct android_chiaki_session_t
jmethodID java_session_event_connected_meth;
jmethodID java_session_event_login_pin_request_meth;
jmethodID java_session_event_quit_meth;
+ jmethodID java_session_event_rumble_meth;
jfieldID java_controller_state_buttons;
jfieldID java_controller_state_l2_state;
jfieldID java_controller_state_r2_state;
@@ -155,6 +141,20 @@ typedef struct android_chiaki_session_t
jfieldID java_controller_state_left_y;
jfieldID java_controller_state_right_x;
jfieldID java_controller_state_right_y;
+ jfieldID java_controller_state_touches;
+ jfieldID java_controller_state_gyro_x;
+ jfieldID java_controller_state_gyro_y;
+ jfieldID java_controller_state_gyro_z;
+ jfieldID java_controller_state_accel_x;
+ jfieldID java_controller_state_accel_y;
+ jfieldID java_controller_state_accel_z;
+ jfieldID java_controller_state_orient_x;
+ jfieldID java_controller_state_orient_y;
+ jfieldID java_controller_state_orient_z;
+ jfieldID java_controller_state_orient_w;
+ jfieldID java_controller_touch_x;
+ jfieldID java_controller_touch_y;
+ jfieldID java_controller_touch_id;
AndroidChiakiVideoDecoder video_decoder;
AndroidChiakiAudioDecoder audio_decoder;
@@ -193,6 +193,14 @@ static void android_chiaki_event_cb(ChiakiEvent *event, void *user)
free(reason_str);
break;
}
+ case CHIAKI_EVENT_RUMBLE:
+ E->CallVoidMethod(env, session->java_session,
+ session->java_session_event_rumble_meth,
+ (jint)event->rumble.left,
+ (jint)event->rumble.right);
+ break;
+ default:
+ break;
}
(*global_vm)->DetachCurrentThread(global_vm);
@@ -213,13 +221,16 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
jclass result_class = E->GetObjectClass(env, result);
jclass connect_info_class = E->GetObjectClass(env, connect_info_obj);
+ jboolean ps5 = E->GetBooleanField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "ps5", "Z"));
jstring host_string = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "host", "Ljava/lang/String;"));
jbyteArray regist_key_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "registKey", "[B"));
jbyteArray morning_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "morning", "[B"));
jobject connect_video_profile_obj = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "videoProfile", "L"BASE_PACKAGE"/ConnectVideoProfile;"));
jclass connect_video_profile_class = E->GetObjectClass(env, connect_video_profile_obj);
- ChiakiConnectInfo connect_info;
+ ChiakiConnectInfo connect_info = { 0 };
+ connect_info.ps5 = ps5;
+
const char *str_borrow = E->GetStringUTFChars(env, host_string, NULL);
connect_info.host = host_str = strdup(str_borrow);
E->ReleaseStringUTFChars(env, host_string, str_borrow);
@@ -254,6 +265,13 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
connect_info.video_profile.max_fps = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "maxFPS", "I"));
connect_info.video_profile.bitrate = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "bitrate", "I"));
+ jobject codec_obj = E->GetObjectField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "codec", "L"BASE_PACKAGE"/Codec;"));
+ jclass codec_class = E->GetObjectClass(env, codec_obj);
+ jint target_value = E->GetIntField(env, codec_obj, E->GetFieldID(env, codec_class, "value", "I"));
+ connect_info.video_profile.codec = (ChiakiCodec)target_value;
+
+ connect_info.video_profile_auto_downgrade = true;
+
session = CHIAKI_NEW(AndroidChiakiSession);
if(!session)
{
@@ -262,7 +280,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
}
memset(session, 0, sizeof(AndroidChiakiSession));
session->log = log;
- err = android_chiaki_video_decoder_init(&session->video_decoder, log, connect_info.video_profile.width, connect_info.video_profile.height);
+ err = android_chiaki_video_decoder_init(&session->video_decoder, log, connect_info.video_profile.width, connect_info.video_profile.height,
+ connect_info.ps5 ? connect_info.video_profile.codec : CHIAKI_CODEC_H264);
if(err != CHIAKI_ERR_SUCCESS)
{
free(session);
@@ -300,6 +319,7 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
session->java_session_event_connected_meth = E->GetMethodID(env, session->java_session_class, "eventConnected", "()V");
session->java_session_event_login_pin_request_meth = E->GetMethodID(env, session->java_session_class, "eventLoginPinRequest", "(Z)V");
session->java_session_event_quit_meth = E->GetMethodID(env, session->java_session_class, "eventQuit", "(ILjava/lang/String;)V");
+ session->java_session_event_rumble_meth = E->GetMethodID(env, session->java_session_class, "eventRumble", "(II)V");
jclass controller_state_class = E->FindClass(env, BASE_PACKAGE"/ControllerState");
session->java_controller_state_buttons = E->GetFieldID(env, controller_state_class, "buttons", "I");
@@ -309,6 +329,22 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
session->java_controller_state_left_y = E->GetFieldID(env, controller_state_class, "leftY", "S");
session->java_controller_state_right_x = E->GetFieldID(env, controller_state_class, "rightX", "S");
session->java_controller_state_right_y = E->GetFieldID(env, controller_state_class, "rightY", "S");
+ session->java_controller_state_touches = E->GetFieldID(env, controller_state_class, "touches", "[L"BASE_PACKAGE"/ControllerTouch;");
+ session->java_controller_state_gyro_x = E->GetFieldID(env, controller_state_class, "gyroX", "F");
+ session->java_controller_state_gyro_y = E->GetFieldID(env, controller_state_class, "gyroY", "F");
+ session->java_controller_state_gyro_z = E->GetFieldID(env, controller_state_class, "gyroZ", "F");
+ session->java_controller_state_accel_x = E->GetFieldID(env, controller_state_class, "accelX", "F");
+ session->java_controller_state_accel_y = E->GetFieldID(env, controller_state_class, "accelY", "F");
+ session->java_controller_state_accel_z = E->GetFieldID(env, controller_state_class, "accelZ", "F");
+ session->java_controller_state_orient_x = E->GetFieldID(env, controller_state_class, "orientX", "F");
+ session->java_controller_state_orient_y = E->GetFieldID(env, controller_state_class, "orientY", "F");
+ session->java_controller_state_orient_z = E->GetFieldID(env, controller_state_class, "orientZ", "F");
+ session->java_controller_state_orient_w = E->GetFieldID(env, controller_state_class, "orientW", "F");
+
+ jclass controller_touch_class = E->FindClass(env, BASE_PACKAGE"/ControllerTouch");
+ session->java_controller_touch_x = E->GetFieldID(env, controller_touch_class, "x", "S");
+ session->java_controller_touch_y = E->GetFieldID(env, controller_touch_class, "y", "S");
+ session->java_controller_touch_id = E->GetFieldID(env, controller_touch_class, "id", "B");
chiaki_session_set_event_cb(&session->session, android_chiaki_event_cb, session);
chiaki_session_set_video_sample_cb(&session->session, android_chiaki_video_decoder_video_sample, &session->video_decoder);
@@ -377,7 +413,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionSetSurface)(JNIEnv *env, jobject obj, jlon
JNIEXPORT void JNICALL JNI_FCN(sessionSetControllerState)(JNIEnv *env, jobject obj, jlong ptr, jobject controller_state_java)
{
AndroidChiakiSession *session = (AndroidChiakiSession *)ptr;
- ChiakiControllerState controller_state = { 0 };
+ ChiakiControllerState controller_state;
+ chiaki_controller_state_set_idle(&controller_state);
controller_state.buttons = (uint32_t)E->GetIntField(env, controller_state_java, session->java_controller_state_buttons);
controller_state.l2_state = (uint8_t)E->GetByteField(env, controller_state_java, session->java_controller_state_l2_state);
controller_state.r2_state = (uint8_t)E->GetByteField(env, controller_state_java, session->java_controller_state_r2_state);
@@ -385,6 +422,34 @@ JNIEXPORT void JNICALL JNI_FCN(sessionSetControllerState)(JNIEnv *env, jobject o
controller_state.left_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_left_y);
controller_state.right_x = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_x);
controller_state.right_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_y);
+ jobjectArray touch_array = E->GetObjectField(env, controller_state_java, session->java_controller_state_touches);
+ size_t touch_array_len = (size_t)E->GetArrayLength(env, touch_array);
+ for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
+ {
+ if(i < touch_array_len)
+ {
+ jobject touch = E->GetObjectArrayElement(env, touch_array, i);
+ controller_state.touches[i].x = (uint16_t)E->GetShortField(env, touch, session->java_controller_touch_x);
+ controller_state.touches[i].y = (uint16_t)E->GetShortField(env, touch, session->java_controller_touch_y);
+ controller_state.touches[i].id = (int8_t)E->GetByteField(env, touch, session->java_controller_touch_id);
+ }
+ else
+ {
+ controller_state.touches[i].x = 0;
+ controller_state.touches[i].y = 0;
+ controller_state.touches[i].id = -1;
+ }
+ }
+ controller_state.gyro_x = E->GetFloatField(env, controller_state_java, session->java_controller_state_gyro_x);
+ controller_state.gyro_y = E->GetFloatField(env, controller_state_java, session->java_controller_state_gyro_y);
+ controller_state.gyro_z = E->GetFloatField(env, controller_state_java, session->java_controller_state_gyro_z);
+ controller_state.accel_x = E->GetFloatField(env, controller_state_java, session->java_controller_state_accel_x);
+ controller_state.accel_y = E->GetFloatField(env, controller_state_java, session->java_controller_state_accel_y);
+ controller_state.accel_z = E->GetFloatField(env, controller_state_java, session->java_controller_state_accel_z);
+ controller_state.orient_x = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_x);
+ controller_state.orient_y = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_y);
+ controller_state.orient_z = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_z);
+ controller_state.orient_w = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_w);
chiaki_session_set_controller_state(&session->session, &controller_state);
}
@@ -589,11 +654,11 @@ JNIEXPORT void JNICALL JNI_FCN(discoveryServiceFree)(JNIEnv *env, jobject obj, j
free(service);
}
-JNIEXPORT jint JNICALL JNI_FCN(discoveryServiceWakeup)(JNIEnv *env, jobject obj, jlong ptr, jstring host_string, jlong user_credential)
+JNIEXPORT jint JNICALL JNI_FCN(discoveryServiceWakeup)(JNIEnv *env, jobject obj, jlong ptr, jstring host_string, jlong user_credential, jboolean ps5)
{
AndroidDiscoveryService *service = (AndroidDiscoveryService *)ptr;
const char *host = E->GetStringUTFChars(env, host_string, NULL);
- ChiakiErrorCode r = chiaki_discovery_wakeup(&global_log, service ? &service->service.discovery : NULL, host, (uint64_t)user_credential);
+ ChiakiErrorCode r = chiaki_discovery_wakeup(&global_log, service ? &service->service.discovery : NULL, host, (uint64_t)user_credential, ps5);
E->ReleaseStringUTFChars(env, host_string, host);
return r;
}
@@ -607,6 +672,8 @@ typedef struct android_chiaki_regist_t
jobject java_regist;
jmethodID java_regist_event_meth;
+ jclass java_target_class;
+
jobject java_regist_event_canceled;
jobject java_regist_event_failed;
jclass java_regist_event_success_class;
@@ -616,6 +683,12 @@ typedef struct android_chiaki_regist_t
jmethodID java_regist_host_ctor;
} AndroidChiakiRegist;
+static jobject create_jni_target(JNIEnv *env, jclass target_class, ChiakiTarget target)
+{
+ jmethodID meth = E->GetStaticMethodID(env, target_class, "fromValue", "(I)L"BASE_PACKAGE"/Target;");
+ return E->CallStaticObjectMethod(env, target_class, meth, (jint)target);
+}
+
static void android_chiaki_regist_cb(ChiakiRegistEvent *event, void *user)
{
AndroidChiakiRegist *regist = user;
@@ -637,12 +710,13 @@ static void android_chiaki_regist_cb(ChiakiRegistEvent *event, void *user)
{
ChiakiRegisteredHost *host = event->registered_host;
jobject java_host = E->NewObject(env, regist->java_regist_host_class, regist->java_regist_host_ctor,
+ create_jni_target(env, regist->java_target_class, host->target),
jnistr_from_ascii(env, host->ap_ssid),
jnistr_from_ascii(env, host->ap_bssid),
jnistr_from_ascii(env, host->ap_key),
jnistr_from_ascii(env, host->ap_name),
- jnibytearray_create(env, host->ps4_mac, sizeof(host->ps4_mac)),
- jnistr_from_ascii(env, host->ps4_nickname),
+ jnibytearray_create(env, host->server_mac, sizeof(host->server_mac)),
+ jnistr_from_ascii(env, host->server_nickname),
jnibytearray_create(env, (const uint8_t *)host->rp_regist_key, sizeof(host->rp_regist_key)),
(jint)host->rp_key_type,
jnibytearray_create(env, host->rp_key, sizeof(host->rp_key)));
@@ -661,6 +735,7 @@ static void android_chiaki_regist_fini_partial(JNIEnv *env, AndroidChiakiRegist
{
android_chiaki_jni_log_fini(®ist->log, env);
E->DeleteGlobalRef(env, regist->java_regist);
+ E->DeleteGlobalRef(env, regist->java_target_class);
E->DeleteGlobalRef(env, regist->java_regist_event_canceled);
E->DeleteGlobalRef(env, regist->java_regist_event_failed);
E->DeleteGlobalRef(env, regist->java_regist_event_success_class);
@@ -683,6 +758,8 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re
regist->java_regist = E->NewGlobalRef(env, java_regist);
regist->java_regist_event_meth = E->GetMethodID(env, E->GetObjectClass(env, regist->java_regist), "event", "(L"BASE_PACKAGE"/RegistEvent;)V");
+ regist->java_target_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/Target"));
+
regist->java_regist_event_canceled = E->NewGlobalRef(env, get_kotlin_global_object(env, BASE_PACKAGE"/RegistEventCanceled"));
regist->java_regist_event_failed = E->NewGlobalRef(env, get_kotlin_global_object(env, BASE_PACKAGE"/RegistEventFailed"));
regist->java_regist_event_success_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/RegistEventSuccess"));
@@ -690,18 +767,24 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re
regist->java_regist_host_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/RegistHost"));
regist->java_regist_host_ctor = E->GetMethodID(env, regist->java_regist_host_class, "", "("
+ "L"BASE_PACKAGE"/Target;" // target: Target
"Ljava/lang/String;" // apSsid: String
"Ljava/lang/String;" // apBssid: String
"Ljava/lang/String;" // apKey: String
"Ljava/lang/String;" // apName: String
- "[B" // ps4Mac: ByteArray
- "Ljava/lang/String;" // ps4Nickname: String
+ "[B" // serverMac: ByteArray
+ "Ljava/lang/String;" // serverNickname: String
"[B" // rpRegistKey: ByteArray
"I" // rpKeyType: UInt
"[B" // rpKey: ByteArray
")V");
jclass regist_info_class = E->GetObjectClass(env, regist_info_obj);
+
+ jobject target_obj = E->GetObjectField(env, regist_info_obj, E->GetFieldID(env, regist_info_class, "target", "L"BASE_PACKAGE"/Target;"));
+ jclass target_class = E->GetObjectClass(env, target_obj);
+ jint target_value = E->GetIntField(env, target_obj, E->GetFieldID(env, target_class, "value", "I"));
+
jstring host_string = E->GetObjectField(env, regist_info_obj, E->GetFieldID(env, regist_info_class, "host", "Ljava/lang/String;"));
jboolean broadcast = E->GetBooleanField(env, regist_info_obj, E->GetFieldID(env, regist_info_class, "broadcast", "Z"));
jstring psn_online_id_string = E->GetObjectField(env, regist_info_obj, E->GetFieldID(env, regist_info_class, "psnOnlineId", "Ljava/lang/String;"));
@@ -709,6 +792,7 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re
jint pin = E->GetIntField(env, regist_info_obj, E->GetFieldID(env, regist_info_class, "pin", "I"));
ChiakiRegistInfo regist_info = { 0 };
+ regist_info.target = (ChiakiTarget)target_value;
regist_info.host = E->GetStringUTFChars(env, host_string, NULL);
regist_info.broadcast = broadcast;
if(psn_online_id_string)
diff --git a/android/app/src/main/cpp/chiaki-jni.h b/android/app/src/main/cpp/chiaki-jni.h
index cf356bc..99383f4 100644
--- a/android/app/src/main/cpp/chiaki-jni.h
+++ b/android/app/src/main/cpp/chiaki-jni.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_JNI_H
#define CHIAKI_JNI_H
diff --git a/android/app/src/main/cpp/circular-buf.hpp b/android/app/src/main/cpp/circular-buf.hpp
index 79c8000..21baf9c 100644
--- a/android/app/src/main/cpp/circular-buf.hpp
+++ b/android/app/src/main/cpp/circular-buf.hpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_JNI_CIRCULARBUF_HPP
#define CHIAKI_JNI_CIRCULARBUF_HPP
diff --git a/android/app/src/main/cpp/log.c b/android/app/src/main/cpp/log.c
index 7ffc568..cbb3c7e 100644
--- a/android/app/src/main/cpp/log.c
+++ b/android/app/src/main/cpp/log.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include "log.h"
diff --git a/android/app/src/main/cpp/log.h b/android/app/src/main/cpp/log.h
index 3d97b6d..19caf35 100644
--- a/android/app/src/main/cpp/log.h
+++ b/android/app/src/main/cpp/log.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_JNI_LOG_H
#define CHIAKI_JNI_LOG_H
diff --git a/android/app/src/main/cpp/oboe b/android/app/src/main/cpp/oboe
index 0ab5b12..8740d0f 160000
--- a/android/app/src/main/cpp/oboe
+++ b/android/app/src/main/cpp/oboe
@@ -1 +1 @@
-Subproject commit 0ab5b12a5bc3630a3d6c83b20eed2a669ebf7a24
+Subproject commit 8740d0fc321a55489dbbf6067298201b7d2e106d
diff --git a/android/app/src/main/cpp/video-decoder.c b/android/app/src/main/cpp/video-decoder.c
index ecf9980..1146ad8 100644
--- a/android/app/src/main/cpp/video-decoder.c
+++ b/android/app/src/main/cpp/video-decoder.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include "video-decoder.h"
@@ -29,36 +14,46 @@
static void *android_chiaki_video_decoder_output_thread_func(void *user);
-ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height)
+ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height, ChiakiCodec codec)
{
decoder->log = log;
decoder->codec = NULL;
decoder->timestamp_cur = 0;
decoder->target_width = target_width;
decoder->target_height = target_height;
+ decoder->target_codec = codec;
+ decoder->shutdown_output = false;
return chiaki_mutex_init(&decoder->codec_mutex, false);
}
+static void kill_decoder(AndroidChiakiVideoDecoder *decoder)
+{
+ chiaki_mutex_lock(&decoder->codec_mutex);
+ decoder->shutdown_output = true;
+ ssize_t codec_buf_index = AMediaCodec_dequeueInputBuffer(decoder->codec, 1000);
+ if(codec_buf_index >= 0)
+ {
+ CHIAKI_LOGI(decoder->log, "Video Decoder sending EOS buffer");
+ AMediaCodec_queueInputBuffer(decoder->codec, (size_t)codec_buf_index, 0, 0, decoder->timestamp_cur++, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
+ AMediaCodec_stop(decoder->codec);
+ chiaki_mutex_unlock(&decoder->codec_mutex);
+ chiaki_thread_join(&decoder->output_thread, NULL);
+ }
+ else
+ {
+ CHIAKI_LOGE(decoder->log, "Failed to get input buffer for shutting down Video Decoder!");
+ AMediaCodec_stop(decoder->codec);
+ chiaki_mutex_unlock(&decoder->codec_mutex);
+ }
+ AMediaCodec_delete(decoder->codec);
+ decoder->codec = NULL;
+ decoder->shutdown_output = false;
+}
+
void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder)
{
if(decoder->codec)
- {
- chiaki_mutex_lock(&decoder->codec_mutex);
- ssize_t codec_buf_index = AMediaCodec_dequeueInputBuffer(decoder->codec, -1);
- if(codec_buf_index >= 0)
- {
- CHIAKI_LOGI(decoder->log, "Video Decoder sending EOS buffer");
- AMediaCodec_queueInputBuffer(decoder->codec, (size_t)codec_buf_index, 0, 0, decoder->timestamp_cur++, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
- chiaki_mutex_unlock(&decoder->codec_mutex);
- chiaki_thread_join(&decoder->output_thread, NULL);
- }
- else
- {
- CHIAKI_LOGE(decoder->log, "Failed to get input buffer for shutting down Video Decoder!");
- chiaki_mutex_unlock(&decoder->codec_mutex);
- }
- AMediaCodec_delete(decoder->codec);
- }
+ kill_decoder(decoder);
chiaki_mutex_fini(&decoder->codec_mutex);
}
@@ -66,6 +61,16 @@ void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder
{
chiaki_mutex_lock(&decoder->codec_mutex);
+ if(!surface)
+ {
+ if(decoder->codec)
+ {
+ kill_decoder(decoder);
+ CHIAKI_LOGI(decoder->log, "Decoder shut down after surface was removed");
+ }
+ return;
+ }
+
if(decoder->codec)
{
#if __ANDROID_API__ >= 23
@@ -82,7 +87,8 @@ void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder
decoder->window = ANativeWindow_fromSurface(env, surface);
- const char *mime = "video/avc";
+ const char *mime = chiaki_codec_is_h265(decoder->target_codec) ? "video/hevc" : "video/avc";
+ CHIAKI_LOGI(decoder->log, "Initializing decoder with mime %s", mime);
decoder->codec = AMediaCodec_createDecoderByType(mime);
if(!decoder->codec)
@@ -196,6 +202,17 @@ static void *android_chiaki_video_decoder_output_thread_func(void *user)
break;
}
}
+ else
+ {
+ chiaki_mutex_lock(&decoder->codec_mutex);
+ bool shutdown = decoder->shutdown_output;
+ chiaki_mutex_unlock(&decoder->codec_mutex);
+ if(shutdown)
+ {
+ CHIAKI_LOGI(decoder->log, "Video Decoder Output Thread detected shutdown after reported error");
+ break;
+ }
+ }
}
CHIAKI_LOGI(decoder->log, "Video Decoder Output Thread exiting");
diff --git a/android/app/src/main/cpp/video-decoder.h b/android/app/src/main/cpp/video-decoder.h
index 9e9e27f..b12ba85 100644
--- a/android/app/src/main/cpp/video-decoder.h
+++ b/android/app/src/main/cpp/video-decoder.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_JNI_VIDEO_DECODER_H
#define CHIAKI_JNI_VIDEO_DECODER_H
@@ -34,11 +19,13 @@ typedef struct android_chiaki_video_decoder_t
ANativeWindow *window;
uint64_t timestamp_cur;
ChiakiThread output_thread;
+ bool shutdown_output;
int32_t target_width;
int32_t target_height;
+ ChiakiCodec target_codec;
} AndroidChiakiVideoDecoder;
-ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height);
+ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height, ChiakiCodec codec);
void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder);
void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder, JNIEnv *env, jobject surface);
bool android_chiaki_video_decoder_video_sample(uint8_t *buf, size_t buf_size, void *user);
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/AppDatabase.kt b/android/app/src/main/java/com/metallic/chiaki/common/AppDatabase.kt
index 165574b..8398f62 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/AppDatabase.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/AppDatabase.kt
@@ -1,33 +1,34 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
import android.content.Context
import androidx.room.*
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.metallic.chiaki.lib.Target
@Database(
- version = 1,
+ version = 2,
entities = [RegisteredHost::class, ManualHost::class])
@TypeConverters(Converters::class)
abstract class AppDatabase: RoomDatabase()
{
abstract fun registeredHostDao(): RegisteredHostDao
abstract fun manualHostDao(): ManualHostDao
+ abstract fun importDao(): ImportDao
+}
+
+val MIGRATION_1_2 = object : Migration(1, 2)
+{
+ override fun migrate(database: SupportSQLiteDatabase)
+ {
+ database.execSQL("ALTER TABLE registered_host ADD target INTEGER NOT NULL DEFAULT 1000")
+ database.execSQL("CREATE TABLE `new_registered_host` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `target` INTEGER NOT NULL, `ap_ssid` TEXT, `ap_bssid` TEXT, `ap_key` TEXT, `ap_name` TEXT, `server_mac` INTEGER NOT NULL, `server_nickname` TEXT, `rp_regist_key` BLOB NOT NULL, `rp_key_type` INTEGER NOT NULL, `rp_key` BLOB NOT NULL)");
+ database.execSQL("INSERT INTO `new_registered_host` SELECT `id`, `target`, `ap_ssid`, `ap_bssid`, `ap_key`, `ap_name`, `ps4_mac`, `ps4_nickname`, `rp_regist_key`, `rp_key_type`, `rp_key` FROM `registered_host`")
+ database.execSQL("DROP TABLE registered_host")
+ database.execSQL("ALTER TABLE new_registered_host RENAME TO registered_host")
+ }
}
private var database: AppDatabase? = null
@@ -39,7 +40,9 @@ fun getDatabase(context: Context): AppDatabase
val db = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
- "chiaki").build()
+ "chiaki")
+ .addMigrations(MIGRATION_1_2)
+ .build()
database = db
return db
}
@@ -51,4 +54,10 @@ private class Converters
@TypeConverter
fun macToValue(addr: MacAddress) = addr.value
+
+ @TypeConverter
+ fun targetFromValue(v: Int) = Target.fromValue(v)
+
+ @TypeConverter
+ fun targetToValue(target: Target) = target.value
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt b/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt
index 30b42d8..a25f0df 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
@@ -25,6 +10,7 @@ sealed class DisplayHost
abstract val host: String
abstract val name: String?
abstract val id: String?
+ abstract val isPS5: Boolean
val isRegistered get() = registeredHost != null
}
@@ -35,8 +21,9 @@ class DiscoveredDisplayHost(
): DisplayHost()
{
override val host get() = discoveredHost.hostAddr ?: ""
- override val name get() = discoveredHost.hostName ?: registeredHost?.ps4Nickname
- override val id get() = discoveredHost.hostId ?: registeredHost?.ps4Mac?.toString()
+ override val name get() = discoveredHost.hostName ?: registeredHost?.serverNickname
+ override val id get() = discoveredHost.hostId ?: registeredHost?.serverMac?.toString()
+ override val isPS5 get() = discoveredHost.isPS5
override fun equals(other: Any?): Boolean =
if(other !is DiscoveredDisplayHost)
@@ -55,8 +42,9 @@ class ManualDisplayHost(
): DisplayHost()
{
override val host get() = manualHost.host
- override val name get() = registeredHost?.ps4Nickname
- override val id get() = registeredHost?.ps4Mac?.toString()
+ override val name get() = registeredHost?.serverNickname
+ override val id get() = registeredHost?.serverMac?.toString()
+ override val isPS5: Boolean get() = registeredHost?.target?.isPS5 ?: false
override fun equals(other: Any?): Boolean =
if(other !is ManualDisplayHost)
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt b/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt
index 01ed0cf..22c4c0c 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/LogManager.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt b/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt
index a648c6b..7927d77 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt
@@ -1,22 +1,10 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
+import com.squareup.moshi.FromJson
+import com.squareup.moshi.JsonDataException
+import com.squareup.moshi.ToJson
import java.nio.ByteBuffer
import java.nio.ByteOrder
@@ -38,6 +26,14 @@ class MacAddress(v: Long)
buf.getLong(0)
})
+ constructor(string: String) : this(
+ (Regex("([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})").matchEntire(string)
+ ?: throw IllegalArgumentException("Invalid MAC Address String"))
+ .groupValues
+ .subList(1, 7)
+ .map { it.toUByte(16).toByte() }
+ .toByteArray())
+
val value: Long = v and 0xffffffffffff
override fun equals(other: Any?): Boolean =
@@ -56,4 +52,10 @@ class MacAddress(v: Long)
(value shr 0x20) and 0xff,
(value shr 0x28) and 0xff
)
+}
+
+class MacAddressJsonAdapter
+{
+ @ToJson fun toJson(macAddress: MacAddress) = macAddress.toString()
+ @FromJson fun fromJson(string: String) = try { MacAddress(string) } catch(e: IllegalArgumentException) { throw JsonDataException(e.message) }
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt b/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt
index c265848..7065610 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/ManualHost.kt
@@ -1,24 +1,9 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
import androidx.room.*
-import androidx.room.ForeignKey.SET_NULL
+import androidx.room.ForeignKey.Companion.SET_NULL
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt
index 17d8da4..3c4f2f9 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
@@ -22,6 +7,7 @@ import android.content.SharedPreferences
import androidx.annotation.StringRes
import androidx.preference.PreferenceManager
import com.metallic.chiaki.R
+import com.metallic.chiaki.lib.Codec
import com.metallic.chiaki.lib.ConnectVideoProfile
import com.metallic.chiaki.lib.VideoFPSPreset
import com.metallic.chiaki.lib.VideoResolutionPreset
@@ -46,12 +32,20 @@ class Preferences(context: Context)
FPS_60("60", R.string.preferences_fps_title_60, VideoFPSPreset.FPS_60)
}
+ enum class Codec(val value: String, @StringRes val title: Int, val codec: com.metallic.chiaki.lib.Codec)
+ {
+ CODEC_H264("h264", R.string.preferences_codec_title_h264, com.metallic.chiaki.lib.Codec.CODEC_H264),
+ CODEC_H265("h265", R.string.preferences_codec_title_h265, com.metallic.chiaki.lib.Codec.CODEC_H265)
+ }
+
companion object
{
val resolutionDefault = Resolution.RES_720P
val resolutionAll = Resolution.values()
val fpsDefault = FPS.FPS_60
val fpsAll = FPS.values()
+ val codecDefault = Codec.CODEC_H265
+ val codecAll = Codec.values()
}
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -74,6 +68,26 @@ class Preferences(context: Context)
get() = sharedPreferences.getBoolean(onScreenControlsEnabledKey, true)
set(value) { sharedPreferences.edit().putBoolean(onScreenControlsEnabledKey, value).apply() }
+ val touchpadOnlyEnabledKey get() = resources.getString(R.string.preferences_touchpad_only_enabled_key)
+ var touchpadOnlyEnabled
+ get() = sharedPreferences.getBoolean(touchpadOnlyEnabledKey, false)
+ set(value) { sharedPreferences.edit().putBoolean(touchpadOnlyEnabledKey, value).apply() }
+
+ val rumbleEnabledKey get() = resources.getString(R.string.preferences_rumble_enabled_key)
+ var rumbleEnabled
+ get() = sharedPreferences.getBoolean(rumbleEnabledKey, true)
+ set(value) { sharedPreferences.edit().putBoolean(rumbleEnabledKey, value).apply() }
+
+ val motionEnabledKey get() = resources.getString(R.string.preferences_motion_enabled_key)
+ var motionEnabled
+ get() = sharedPreferences.getBoolean(motionEnabledKey, true)
+ set(value) { sharedPreferences.edit().putBoolean(motionEnabledKey, value).apply() }
+
+ val buttonHapticEnabledKey get() = resources.getString(R.string.preferences_button_haptic_enabled_key)
+ var buttonHapticEnabled
+ get() = sharedPreferences.getBoolean(buttonHapticEnabledKey, true)
+ set(value) { sharedPreferences.edit().putBoolean(buttonHapticEnabledKey, value).apply() }
+
val logVerboseKey get() = resources.getString(R.string.preferences_log_verbose_key)
var logVerbose
get() = sharedPreferences.getBoolean(logVerboseKey, false)
@@ -107,12 +121,19 @@ class Preferences(context: Context)
private val bitrateAutoSubject by lazy { BehaviorSubject.createDefault(bitrateAuto) }
val bitrateAutoObservable: Observable get() = bitrateAutoSubject
- private val videoProfileDefaultBitrate get() = ConnectVideoProfile.preset(resolution.preset, fps.preset)
+ val codecKey get() = resources.getString(R.string.preferences_codec_key)
+ var codec
+ get() = sharedPreferences.getString(codecKey, codecDefault.value)?.let { value ->
+ Codec.values().firstOrNull { it.value == value }
+ } ?: codecDefault
+ set(value) { sharedPreferences.edit().putString(codecKey, value.value).apply() }
+
+ private val videoProfileDefaultBitrate get() = ConnectVideoProfile.preset(resolution.preset, fps.preset, codec.codec)
val videoProfile get() = videoProfileDefaultBitrate.let {
val bitrate = bitrate
if(bitrate == null)
it
else
- ConnectVideoProfile(it.width, it.height, it.maxFPS, bitrate)
+ it.copy(bitrate = bitrate)
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt b/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt
index a0c83e0..d920a8c 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/RegisteredHost.kt
@@ -1,25 +1,11 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common
import androidx.room.*
-import androidx.room.ColumnInfo.BLOB
+import androidx.room.ColumnInfo.Companion.BLOB
import com.metallic.chiaki.lib.RegistHost
+import com.metallic.chiaki.lib.Target
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Maybe
@@ -28,24 +14,26 @@ import io.reactivex.Single
@Entity(tableName = "registered_host")
data class RegisteredHost(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
+ @ColumnInfo(name = "target") val target: Target,
@ColumnInfo(name = "ap_ssid") val apSsid: String?,
@ColumnInfo(name = "ap_bssid") val apBssid: String?,
@ColumnInfo(name = "ap_key") val apKey: String?,
@ColumnInfo(name = "ap_name") val apName: String?,
- @ColumnInfo(name = "ps4_mac") val ps4Mac: MacAddress,
- @ColumnInfo(name = "ps4_nickname") val ps4Nickname: String?,
+ @ColumnInfo(name = "server_mac") val serverMac: MacAddress,
+ @ColumnInfo(name = "server_nickname") val serverNickname: String?,
@ColumnInfo(name = "rp_regist_key", typeAffinity = BLOB) val rpRegistKey: ByteArray, // CHIAKI_SESSION_AUTH_SIZE
@ColumnInfo(name = "rp_key_type") val rpKeyType: Int,
@ColumnInfo(name = "rp_key", typeAffinity = BLOB) val rpKey: ByteArray // 0x10
)
{
constructor(registHost: RegistHost) : this(
+ target = registHost.target,
apSsid = registHost.apSsid,
apBssid = registHost.apBssid,
apKey = registHost.apKey,
apName = registHost.apName,
- ps4Mac = MacAddress(registHost.ps4Mac),
- ps4Nickname = registHost.ps4Nickname,
+ serverMac = MacAddress(registHost.serverMac),
+ serverNickname = registHost.serverNickname,
rpRegistKey = registHost.rpRegistKey,
rpKeyType = registHost.rpKeyType.toInt(),
rpKey = registHost.rpKey
@@ -59,12 +47,13 @@ data class RegisteredHost(
other as RegisteredHost
if(id != other.id) return false
+ if(target != other.target) return false
if(apSsid != other.apSsid) return false
if(apBssid != other.apBssid) return false
if(apKey != other.apKey) return false
if(apName != other.apName) return false
- if(ps4Mac != other.ps4Mac) return false
- if(ps4Nickname != other.ps4Nickname) return false
+ if(serverMac != other.serverMac) return false
+ if(serverNickname != other.serverNickname) return false
if(!rpRegistKey.contentEquals(other.rpRegistKey)) return false
if(rpKeyType != other.rpKeyType) return false
if(!rpKey.contentEquals(other.rpKey)) return false
@@ -75,12 +64,13 @@ data class RegisteredHost(
override fun hashCode(): Int
{
var result = id.hashCode()
+ result = 31 * result + target.hashCode()
result = 31 * result + (apSsid?.hashCode() ?: 0)
result = 31 * result + (apBssid?.hashCode() ?: 0)
result = 31 * result + (apKey?.hashCode() ?: 0)
result = 31 * result + (apName?.hashCode() ?: 0)
- result = 31 * result + ps4Mac.hashCode()
- result = 31 * result + (ps4Nickname?.hashCode() ?: 0)
+ result = 31 * result + serverMac.hashCode()
+ result = 31 * result + (serverNickname?.hashCode() ?: 0)
result = 31 * result + rpRegistKey.contentHashCode()
result = 31 * result + rpKeyType
result = 31 * result + rpKey.contentHashCode()
@@ -94,10 +84,10 @@ interface RegisteredHostDao
@Query("SELECT * FROM registered_host")
fun getAll(): Flowable>
- @Query("SELECT * FROM registered_host WHERE ps4_mac == :mac LIMIT 1")
+ @Query("SELECT * FROM registered_host WHERE server_mac == :mac LIMIT 1")
fun getByMac(mac: MacAddress): Maybe
- @Query("DELETE FROM registered_host WHERE ps4_mac == :mac")
+ @Query("DELETE FROM registered_host WHERE server_mac == :mac")
fun deleteByMac(mac: MacAddress): Completable
@Delete
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt
new file mode 100644
index 0000000..74257ac
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+package com.metallic.chiaki.common
+
+import android.app.Activity
+import android.content.ClipData
+import android.content.Intent
+import android.net.Uri
+import android.util.Base64
+import android.util.Log
+import androidx.core.content.FileProvider
+import androidx.room.*
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.metallic.chiaki.R
+import com.metallic.chiaki.lib.Target
+import com.squareup.moshi.*
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.disposables.Disposable
+import io.reactivex.rxkotlin.Singles
+import io.reactivex.rxkotlin.addTo
+import io.reactivex.schedulers.Schedulers
+import okio.Buffer
+import okio.Okio
+import okio.buffer
+import okio.source
+import java.io.File
+import java.io.IOException
+
+@JsonClass(generateAdapter = true)
+class SerializedRegisteredHost(
+ @Json(name = "target") val target: Target,
+ @Json(name = "ap_ssid") val apSsid: String?,
+ @Json(name = "ap_bssid") val apBssid: String?,
+ @Json(name = "ap_key") val apKey: String?,
+ @Json(name = "ap_name") val apName: String?,
+ @Json(name = "server_mac") val serverMac: MacAddress,
+ @Json(name = "server_nickname") val serverNickname: String?,
+ @Json(name = "rp_regist_key") val rpRegistKey: ByteArray,
+ @Json(name = "rp_key_type") val rpKeyType: Int,
+ @Json(name = "rp_key") val rpKey: ByteArray
+){
+ constructor(registeredHost: RegisteredHost) : this(
+ registeredHost.target,
+ registeredHost.apSsid,
+ registeredHost.apBssid,
+ registeredHost.apKey,
+ registeredHost.apName,
+ registeredHost.serverMac,
+ registeredHost.serverNickname,
+ registeredHost.rpRegistKey,
+ registeredHost.rpKeyType,
+ registeredHost.rpKey
+ )
+}
+
+@JsonClass(generateAdapter = true)
+class SerializedManualHost(
+ @Json(name = "host") val host: String,
+ @Json(name = "server_mac") val serverMac: MacAddress?
+)
+
+@JsonClass(generateAdapter = true)
+data class SerializedSettings(
+ @Json(name = "registered_hosts") val registeredHosts: List,
+ @Json(name = "manual_hosts") val manualHosts: List
+)
+{
+ companion object
+ {
+ fun fromDatabase(db: AppDatabase) = Singles.zip(
+ db.registeredHostDao().getAll().firstOrError(),
+ db.manualHostDao().getAll().firstOrError()
+ ) { registeredHosts, manualHosts ->
+ SerializedSettings(
+ registeredHosts.map { SerializedRegisteredHost(it) },
+ manualHosts.map { manualHost ->
+ SerializedManualHost(
+ manualHost.host,
+ manualHost.registeredHost?.let { registeredHostId ->
+ registeredHosts.firstOrNull { it.id == registeredHostId }
+ }?.serverMac
+ )
+ })
+ }
+ }
+}
+
+private class ByteArrayJsonAdapter
+{
+ @ToJson fun toJson(byteArray: ByteArray) = Base64.encodeToString(byteArray, Base64.NO_WRAP)
+ @FromJson fun fromJson(string: String) = Base64.decode(string, Base64.DEFAULT)
+}
+
+private fun moshi() =
+ Moshi.Builder()
+ .add(MacAddressJsonAdapter())
+ .add(ByteArrayJsonAdapter())
+ .build()
+
+private fun Moshi.serializedSettingsAdapter() =
+ adapter(SerializedSettings::class.java)
+ .serializeNulls()
+
+private const val KEY_FORMAT = "format"
+private const val FORMAT = "chiaki-settings"
+private const val KEY_VERSION = "version"
+private const val VERSION = 2
+private const val KEY_SETTINGS = "settings"
+
+fun exportAllSettings(db: AppDatabase) = SerializedSettings.fromDatabase(db)
+ .subscribeOn(Schedulers.io())
+ .map {
+ val buffer = Buffer()
+ val writer = JsonWriter.of(buffer)
+ val adapter = moshi().serializedSettingsAdapter()
+ writer.indent = " "
+ writer.
+ beginObject()
+ .name(KEY_FORMAT).value(FORMAT)
+ .name(KEY_VERSION).value(VERSION)
+ writer.name(KEY_SETTINGS)
+ adapter.toJson(writer, it)
+ writer.endObject()
+ buffer.readUtf8()
+ }
+
+fun exportAndShareAllSettings(activity: Activity): Disposable
+{
+ val db = getDatabase(activity)
+ val dir = File(activity.cacheDir, "export_settings")
+ dir.mkdirs()
+ val file = File(dir, "chiaki-settings.json")
+ return exportAllSettings(db)
+ .map {
+ file.writeText(it, Charsets.UTF_8)
+ file
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe { _ ->
+ val uri = FileProvider.getUriForFile(activity, fileProviderAuthority, file)
+ Intent(Intent.ACTION_SEND).also {
+ it.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ it.type = "application/json"
+ it.putExtra(Intent.EXTRA_STREAM, uri)
+ it.clipData = ClipData.newRawUri("", uri)
+ activity.startActivity(Intent.createChooser(it, activity.getString(R.string.action_share_log)))
+ }
+ }
+}
+
+fun importSettingsFromUri(activity: Activity, uri: Uri, disposable: CompositeDisposable)
+{
+ fun loadFail(msg: String)
+ {
+ MaterialAlertDialogBuilder(activity)
+ .setMessage(activity.getString(R.string.alert_message_import_failed, msg))
+ .setPositiveButton(R.string.action_import_failed_ack) { _, _ -> }
+ .create()
+ .show()
+ }
+
+ try
+ {
+ val inputStream = activity.contentResolver.openInputStream(uri) ?: throw IOException()
+ val buffer = inputStream.source().buffer()
+ val reader = JsonReader.of(buffer)
+ val adapter = moshi().serializedSettingsAdapter()
+
+ var format: String? = null
+ var version: Int? = null
+ var settingsValue: Any? = null
+
+ reader.beginObject()
+ while(reader.hasNext())
+ {
+ when(reader.nextName())
+ {
+ KEY_FORMAT -> format = reader.nextString()
+ KEY_VERSION -> version = reader.nextInt()
+ KEY_SETTINGS -> settingsValue = reader.readJsonValue()
+ }
+ }
+ reader.endObject()
+
+ if(format == null || version == null || settingsValue == null)
+ throw IOException("Missing format, version or settings from JSON")
+ if(format != FORMAT)
+ throw IOException("Value of format is invalid")
+ if(version != VERSION) // Add migrations here when necessary
+ throw IOException("Value of version is invalid")
+
+ val settings = adapter.fromJsonValue(settingsValue) ?: throw JsonDataException("Failed to parse Settings JSON")
+ Log.i("SerializedSettings", "would import: $settings")
+
+ MaterialAlertDialogBuilder(activity)
+ .setMessage(activity.getString(R.string.alert_message_import,
+ settings.registeredHosts.let {
+ if(it.isEmpty())
+ "-"
+ else
+ it.joinToString(separator = "") { host -> "\n - ${host.serverNickname ?: "?"} / ${host.serverMac}" }
+ },
+ settings.manualHosts.let {
+ if(it.isEmpty())
+ "-"
+ else
+ it.joinToString(separator = "") { host -> "\n - ${host.host} / ${host.serverMac ?: "unregistered"}" }
+ }
+ ))
+ .setTitle(R.string.alert_title_import)
+ .setNegativeButton(R.string.action_import_cancel) { _, _ -> }
+ .setPositiveButton(R.string.action_import_import) { _, _ ->
+ getDatabase(activity).importDao()
+ .importCompletable(settings)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe()
+ .addTo(disposable)
+ }
+ .create()
+ .show()
+ }
+ catch(e: IOException)
+ {
+ e.printStackTrace()
+ loadFail(e.message ?: "")
+ }
+ catch(e: JsonDataException)
+ {
+ e.printStackTrace()
+ loadFail(e.message ?: "")
+ }
+}
+
+@Dao
+abstract class ImportDao
+{
+ @Insert
+ abstract fun insertRegisteredHosts(hosts: List)
+
+ @Insert
+ abstract fun insertManualHosts(hosts: List)
+
+ class IdWithMac(val id: Long, val mac: MacAddress)
+
+ @Query("SELECT id, server_mac AS mac FROM registered_host WHERE server_mac IN (:macs)")
+ abstract fun registeredHostsByMac(macs: List): List
+
+ @Transaction
+ fun import(settings: SerializedSettings)
+ {
+ insertRegisteredHosts(
+ settings.registeredHosts.map {
+ RegisteredHost(
+ target = it.target,
+ apSsid = it.apSsid,
+ apBssid = it.apBssid,
+ apKey = it.apKey,
+ apName = it.apName,
+ serverMac = it.serverMac,
+ serverNickname = it.serverNickname,
+ rpRegistKey = it.rpRegistKey,
+ rpKeyType = it.rpKeyType,
+ rpKey = it.rpKey
+ )
+ })
+
+ val macs = settings.manualHosts.mapNotNull { it.serverMac }
+ val idMacs =
+ if(macs.isNotEmpty())
+ registeredHostsByMac(macs)
+ else
+ listOf()
+
+ insertManualHosts(
+ settings.manualHosts.map {
+ ManualHost(
+ host = it.host,
+ registeredHost = idMacs.firstOrNull { regHost -> regHost.mac == it.serverMac }?.id
+ )
+ })
+ }
+}
+
+private fun ImportDao.importCompletable(settings: SerializedSettings) = Completable.fromCallable { import(settings) }
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/ext/RevealActivity.kt b/android/app/src/main/java/com/metallic/chiaki/common/ext/RevealActivity.kt
index db4faac..321e0bd 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/ext/RevealActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/ext/RevealActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common.ext
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/ext/RxLiveData.kt b/android/app/src/main/java/com/metallic/chiaki/common/ext/RxLiveData.kt
index 6dc5ebf..bd98458 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/ext/RxLiveData.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/ext/RxLiveData.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common.ext
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/ext/StringHex.kt b/android/app/src/main/java/com/metallic/chiaki/common/ext/StringHex.kt
index 8a5f64b..6212ddc 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/ext/StringHex.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/ext/StringHex.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common.ext
diff --git a/android/app/src/main/java/com/metallic/chiaki/common/ext/ViewGroupInflate.kt b/android/app/src/main/java/com/metallic/chiaki/common/ext/ViewGroupInflate.kt
index b811a5c..0b613cc 100644
--- a/android/app/src/main/java/com/metallic/chiaki/common/ext/ViewGroupInflate.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/common/ext/ViewGroupInflate.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.common.ext
diff --git a/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt b/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt
index fceb1ad..e7b2e3e 100644
--- a/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.discovery
@@ -36,7 +21,7 @@ import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.util.concurrent.TimeUnit
-val DiscoveryHost.ps4Mac get() = this.hostId?.hexToByteArray()?.let {
+val DiscoveryHost.serverMac get() = this.hostId?.hexToByteArray()?.let {
if(it.size == MacAddress.LENGTH)
MacAddress(it)
else
@@ -107,14 +92,14 @@ class DiscoveryManager
disposable.dispose()
}
- fun sendWakeup(host: String, registKey: ByteArray)
+ fun sendWakeup(host: String, registKey: ByteArray, ps5: Boolean)
{
val registKeyString = registKey.indexOfFirst { it == 0.toByte() }.let { end -> registKey.copyOfRange(0, if(end >= 0) end else registKey.size) }.toString(StandardCharsets.UTF_8)
val credential = try { registKeyString.toULong(16) } catch(e: NumberFormatException) {
Log.e("DiscoveryManager", "Failed to convert registKey to int", e)
return
}
- DiscoveryService.wakeup(discoveryService, host, credential)
+ DiscoveryService.wakeup(discoveryService, host, credential, ps5)
}
private fun updateService()
diff --git a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt
index e511e22..bb0b0a6 100644
--- a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt
@@ -3,11 +3,29 @@ package com.metallic.chiaki.lib
import android.os.Parcelable
import android.util.Log
import android.view.Surface
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
import java.lang.Exception
import java.net.InetSocketAddress
import kotlin.math.abs
+enum class Target(val value: Int)
+{
+ PS4_UNKNOWN(0),
+ PS4_8(800),
+ PS4_9(900),
+ PS4_10(1000),
+ PS5_UNKNOWN(1000000),
+ PS5_1(1000100);
+
+ companion object
+ {
+ @JvmStatic
+ fun fromValue(value: Int) = values().firstOrNull { it.value == value } ?: PS4_10
+ }
+
+ val isPS5 get() = value >= PS5_UNKNOWN.value
+}
+
enum class VideoResolutionPreset(val value: Int)
{
RES_360P(1),
@@ -22,23 +40,32 @@ enum class VideoFPSPreset(val value: Int)
FPS_60(60)
}
+enum class Codec(val value: Int)
+{
+ CODEC_H264(0),
+ CODEC_H265(1),
+ CODEC_H265_HDR(2)
+}
+
@Parcelize
data class ConnectVideoProfile(
val width: Int,
val height: Int,
val maxFPS: Int,
- val bitrate: Int
+ val bitrate: Int,
+ val codec: Codec
): Parcelable
{
companion object
{
- fun preset(resolutionPreset: VideoResolutionPreset, fpsPreset: VideoFPSPreset)
- = ChiakiNative.videoProfilePreset(resolutionPreset.value, fpsPreset.value)
+ fun preset(resolutionPreset: VideoResolutionPreset, fpsPreset: VideoFPSPreset, codec: Codec)
+ = ChiakiNative.videoProfilePreset(resolutionPreset.value, fpsPreset.value, codec)
}
}
@Parcelize
data class ConnectInfo(
+ val ps5: Boolean,
val host: String,
val registKey: ByteArray,
val morning: ByteArray,
@@ -56,19 +83,19 @@ private class ChiakiNative
}
@JvmStatic external fun errorCodeToString(value: Int): String
@JvmStatic external fun quitReasonToString(value: Int): String
- @JvmStatic external fun quitReasonIsStopped(value: Int): Boolean
- @JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int): ConnectVideoProfile
+ @JvmStatic external fun quitReasonIsError(value: Int): Boolean
+ @JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int, codec: Codec): ConnectVideoProfile
@JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean, javaSession: Session)
@JvmStatic external fun sessionFree(ptr: Long)
@JvmStatic external fun sessionStart(ptr: Long): Int
@JvmStatic external fun sessionStop(ptr: Long): Int
@JvmStatic external fun sessionJoin(ptr: Long): Int
- @JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface)
+ @JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface?)
@JvmStatic external fun sessionSetControllerState(ptr: Long, controllerState: ControllerState)
@JvmStatic external fun sessionSetLoginPin(ptr: Long, pin: String)
@JvmStatic external fun discoveryServiceCreate(result: CreateResult, options: DiscoveryServiceOptions, javaService: DiscoveryService)
@JvmStatic external fun discoveryServiceFree(ptr: Long)
- @JvmStatic external fun discoveryServiceWakeup(ptr: Long, host: String, userCredential: Long)
+ @JvmStatic external fun discoveryServiceWakeup(ptr: Long, host: String, userCredential: Long, ps5: Boolean)
@JvmStatic external fun registStart(result: CreateResult, registInfo: RegistInfo, javaLog: ChiakiLog, javaRegist: Regist)
@JvmStatic external fun registStop(ptr: Long)
@JvmStatic external fun registFree(ptr: Long)
@@ -122,6 +149,14 @@ class ChiakiLog(val levelMask: Int, val callback: (level: Int, text: String) ->
private fun maxAbs(a: Short, b: Short) = if(abs(a.toInt()) > abs(b.toInt())) a else b
+private val CONTROLLER_TOUCHES_MAX = 2 // must be the same as CHIAKI_CONTROLLER_TOUCHES_MAX
+
+data class ControllerTouch(
+ var x: UShort = 0U,
+ var y: UShort = 0U,
+ var id: Byte = -1 // -1 = up
+)
+
data class ControllerState constructor(
var buttons: UInt = 0U,
var l2State: UByte = 0U,
@@ -129,26 +164,40 @@ data class ControllerState constructor(
var leftX: Short = 0,
var leftY: Short = 0,
var rightX: Short = 0,
- var rightY: Short = 0
+ var rightY: Short = 0,
+ private var touchIdNext: UByte = 0U,
+ var touches: Array = arrayOf(ControllerTouch(), ControllerTouch()),
+ var gyroX: Float = 0.0f,
+ var gyroY: Float = 0.0f,
+ var gyroZ: Float = 0.0f,
+ var accelX: Float = 0.0f,
+ var accelY: Float = 1.0f,
+ var accelZ: Float = 0.0f,
+ var orientX: Float = 0.0f,
+ var orientY: Float = 0.0f,
+ var orientZ: Float = 0.0f,
+ var orientW: Float = 1.0f
){
companion object
{
val BUTTON_CROSS = (1 shl 0).toUInt()
val BUTTON_MOON = (1 shl 1).toUInt()
- val BUTTON_BOX = (1 shl 2).toUInt()
- val BUTTON_PYRAMID = (1 shl 3).toUInt()
+ val BUTTON_BOX = (1 shl 2).toUInt()
+ val BUTTON_PYRAMID = (1 shl 3).toUInt()
val BUTTON_DPAD_LEFT = (1 shl 4).toUInt()
val BUTTON_DPAD_RIGHT = (1 shl 5).toUInt()
- val BUTTON_DPAD_UP = (1 shl 6).toUInt()
+ val BUTTON_DPAD_UP = (1 shl 6).toUInt()
val BUTTON_DPAD_DOWN = (1 shl 7).toUInt()
- val BUTTON_L1 = (1 shl 8).toUInt()
- val BUTTON_R1 = (1 shl 9).toUInt()
+ val BUTTON_L1 = (1 shl 8).toUInt()
+ val BUTTON_R1 = (1 shl 9).toUInt()
val BUTTON_L3 = (1 shl 10).toUInt()
val BUTTON_R3 = (1 shl 11).toUInt()
- val BUTTON_OPTIONS = (1 shl 12).toUInt()
+ val BUTTON_OPTIONS = (1 shl 12).toUInt()
val BUTTON_SHARE = (1 shl 13).toUInt()
- val BUTTON_TOUCHPAD = (1 shl 14).toUInt()
+ val BUTTON_TOUCHPAD = (1 shl 14).toUInt()
val BUTTON_PS = (1 shl 15).toUInt()
+ val TOUCHPAD_WIDTH: UShort = 1920U
+ val TOUCHPAD_HEIGHT: UShort = 942U
}
infix fun or(o: ControllerState) = ControllerState(
@@ -158,24 +207,116 @@ data class ControllerState constructor(
leftX = maxAbs(leftX, o.leftX),
leftY = maxAbs(leftY, o.leftY),
rightX = maxAbs(rightX, o.rightX),
- rightY = maxAbs(rightY, o.rightY)
+ rightY = maxAbs(rightY, o.rightY),
+ touches = touches.zip(o.touches) { a, b -> if(a.id >= 0) a else b }.toTypedArray(),
+ gyroX = gyroX,
+ gyroY = gyroY,
+ gyroZ = gyroZ,
+ accelX = accelX,
+ accelY = accelY,
+ accelZ = accelZ,
+ orientX = orientX,
+ orientY = orientY,
+ orientZ = orientZ,
+ orientW = orientW
)
+
+ override fun equals(other: Any?): Boolean
+ {
+ if(this === other) return true
+ if(javaClass != other?.javaClass) return false
+
+ other as ControllerState
+
+ if(buttons != other.buttons) return false
+ if(l2State != other.l2State) return false
+ if(r2State != other.r2State) return false
+ if(leftX != other.leftX) return false
+ if(leftY != other.leftY) return false
+ if(rightX != other.rightX) return false
+ if(rightY != other.rightY) return false
+ if(touchIdNext != other.touchIdNext) return false
+ if(!touches.contentEquals(other.touches)) return false
+ if(gyroX != other.gyroX) return false
+ if(gyroY != other.gyroY) return false
+ if(gyroZ != other.gyroZ) return false
+ if(accelX != other.accelX) return false
+ if(accelY != other.accelY) return false
+ if(accelZ != other.accelZ) return false
+ if(orientX != other.orientX) return false
+ if(orientY != other.orientY) return false
+ if(orientZ != other.orientZ) return false
+ if(orientW != other.orientW) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int
+ {
+ var result = buttons.hashCode()
+ result = 31 * result + l2State.hashCode()
+ result = 31 * result + r2State.hashCode()
+ result = 31 * result + leftX
+ result = 31 * result + leftY
+ result = 31 * result + rightX
+ result = 31 * result + rightY
+ result = 31 * result + touchIdNext.hashCode()
+ result = 31 * result + touches.contentHashCode()
+ result = 31 * result + gyroX.hashCode()
+ result = 31 * result + gyroY.hashCode()
+ result = 31 * result + gyroZ.hashCode()
+ result = 31 * result + accelX.hashCode()
+ result = 31 * result + accelY.hashCode()
+ result = 31 * result + accelZ.hashCode()
+ result = 31 * result + orientX.hashCode()
+ result = 31 * result + orientY.hashCode()
+ result = 31 * result + orientZ.hashCode()
+ result = 31 * result + orientW.hashCode()
+ return result
+ }
+
+ fun startTouch(x: UShort, y: UShort): UByte? =
+ touches
+ .find { it.id < 0 }
+ ?.also {
+ it.id = touchIdNext.toByte()
+ it.x = x
+ it.y = y
+ touchIdNext = ((touchIdNext + 1U) and 0x7fU).toUByte()
+ }?.id?.toUByte()
+
+ fun stopTouch(id: UByte)
+ {
+ touches.find {
+ it.id >= 0 && it.id == id.toByte()
+ }?.let {
+ it.id = -1
+ }
+ }
+
+ fun setTouchPos(id: UByte, x: UShort, y: UShort): Boolean
+ = touches.find {
+ it.id >= 0 && it.id == id.toByte()
+ }?.let {
+ val r = it.x != x || it.y != y
+ it.x = x
+ it.y = y
+ r
+ } ?: false
}
class QuitReason(val value: Int)
{
override fun toString() = ChiakiNative.quitReasonToString(value)
- /**
- * whether the reason is CHIAKI_QUIT_REASON_STOPPED
- */
- val isStopped = ChiakiNative.quitReasonIsStopped(value)
+ val isError = ChiakiNative.quitReasonIsError(value)
}
sealed class Event
object ConnectedEvent: Event()
data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event()
data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event()
+data class RumbleEvent(val left: UByte, val right: UByte): Event()
class CreateError(val errorCode: ErrorCode): Exception("Failed to create a native object: $errorCode")
@@ -231,7 +372,12 @@ class Session(connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean)
event(QuitEvent(QuitReason(reasonValue), reasonString))
}
- fun setSurface(surface: Surface)
+ private fun eventRumble(left: Int, right: Int)
+ {
+ event(RumbleEvent(left.toUByte(), right.toUByte()))
+ }
+
+ fun setSurface(surface: Surface?)
{
ChiakiNative.sessionSetSurface(nativePtr, surface)
}
@@ -265,6 +411,8 @@ data class DiscoveryHost(
READY,
STANDBY
}
+
+ val isPS5 get() = deviceDiscoveryProtocolVersion == "00030010"
}
@@ -281,8 +429,8 @@ class DiscoveryService(
{
companion object
{
- fun wakeup(service: DiscoveryService?, host: String, userCredential: ULong) =
- ChiakiNative.discoveryServiceWakeup(service?.nativePtr ?: 0, host, userCredential.toLong())
+ fun wakeup(service: DiscoveryService?, host: String, userCredential: ULong, ps5: Boolean) =
+ ChiakiNative.discoveryServiceWakeup(service?.nativePtr ?: 0, host, userCredential.toLong(), ps5)
}
private var nativePtr: Long
@@ -316,6 +464,7 @@ class DiscoveryService(
@Parcelize
data class RegistInfo(
+ val target: Target,
val host: String,
val broadcast: Boolean,
val psnOnlineId: String?,
@@ -330,12 +479,13 @@ data class RegistInfo(
}
data class RegistHost(
+ val target: Target,
val apSsid: String,
val apBssid: String,
val apKey: String,
val apName: String,
- val ps4Mac: ByteArray,
- val ps4Nickname: String,
+ val serverMac: ByteArray,
+ val serverNickname: String,
val rpRegistKey: ByteArray,
val rpKeyType: UInt,
val rpKey: ByteArray
diff --git a/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt b/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt
index b37eb4f..465a2a7 100644
--- a/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt
@@ -1,23 +1,9 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.main
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
@@ -31,8 +17,8 @@ import com.metallic.chiaki.common.DiscoveredDisplayHost
import com.metallic.chiaki.common.DisplayHost
import com.metallic.chiaki.common.ManualDisplayHost
import com.metallic.chiaki.common.ext.inflate
+import com.metallic.chiaki.databinding.ItemDisplayHostBinding
import com.metallic.chiaki.lib.DiscoveryHost
-import kotlinx.android.synthetic.main.item_display_host.view.*
class DisplayHostDiffCallback(val old: List, val new: List): DiffUtil.Callback()
{
@@ -57,10 +43,10 @@ class DisplayHostRecyclerViewAdapter(
diff.dispatchUpdatesTo(this)
}
- class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+ class ViewHolder(val binding: ItemDisplayHostBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
- = ViewHolder(parent.inflate(R.layout.item_display_host))
+ = ViewHolder(ItemDisplayHostBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = hosts.count()
@@ -68,7 +54,7 @@ class DisplayHostRecyclerViewAdapter(
{
val context = holder.itemView.context
val host = hosts[position]
- holder.itemView.also {
+ holder.binding.also {
it.nameTextView.text = host.name
it.hostTextView.text = context.getString(R.string.display_host_host, host.host)
val id = host.id
@@ -90,16 +76,19 @@ class DisplayHostRecyclerViewAdapter(
} ?: ""
it.discoveredIndicatorLayout.visibility = if(host is DiscoveredDisplayHost) View.VISIBLE else View.GONE
it.stateIndicatorImageView.setImageResource(
- if(host is DiscoveredDisplayHost)
- when(host.discoveredHost.state)
+ when
+ {
+ host is DiscoveredDisplayHost -> when(host.discoveredHost.state)
{
- DiscoveryHost.State.STANDBY -> R.drawable.ic_console_standby
- DiscoveryHost.State.READY -> R.drawable.ic_console_ready
- else -> R.drawable.ic_console
+ DiscoveryHost.State.STANDBY -> if(host.isPS5) R.drawable.ic_console_ps5_standby else R.drawable.ic_console_standby
+ DiscoveryHost.State.READY -> if(host.isPS5) R.drawable.ic_console_ps5_ready else R.drawable.ic_console_ready
+ else -> if(host.isPS5) R.drawable.ic_console_ps5 else R.drawable.ic_console
}
- else
- R.drawable.ic_console)
- it.setOnClickListener { clickCallback(host) }
+ host.isPS5 -> R.drawable.ic_console_ps5
+ else -> R.drawable.ic_console
+ }
+ )
+ it.root.setOnClickListener { clickCallback(host) }
val canWakeup = host.registeredHost != null
val canEditDelete = host is ManualDisplayHost
diff --git a/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonBackgroundBehavior.kt b/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonBackgroundBehavior.kt
index 5a94ed6..3babfa9 100644
--- a/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonBackgroundBehavior.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonBackgroundBehavior.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.main
diff --git a/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonSpeedDialBehavior.kt b/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonSpeedDialBehavior.kt
index bf9294f..59a2003 100644
--- a/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonSpeedDialBehavior.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/main/FloatingActionButtonSpeedDialBehavior.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.main
diff --git a/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt b/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt
index 9e85b91..5bf1f49 100644
--- a/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.main
@@ -25,60 +10,61 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.metallic.chiaki.R
import com.metallic.chiaki.common.*
import com.metallic.chiaki.common.ext.putRevealExtra
import com.metallic.chiaki.common.ext.viewModelFactory
+import com.metallic.chiaki.databinding.ActivityMainBinding
import com.metallic.chiaki.lib.ConnectInfo
import com.metallic.chiaki.lib.DiscoveryHost
import com.metallic.chiaki.manualconsole.EditManualConsoleActivity
import com.metallic.chiaki.regist.RegistActivity
import com.metallic.chiaki.settings.SettingsActivity
import com.metallic.chiaki.stream.StreamActivity
-import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity()
{
private lateinit var viewModel: MainViewModel
+ private lateinit var binding: ActivityMainBinding
private var discoveryMenuItem: MenuItem? = null
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
title = ""
- setSupportActionBar(toolbar)
+ setSupportActionBar(binding.toolbar)
- floatingActionButton.setOnClickListener {
- expandFloatingActionButton(!floatingActionButton.isExpanded)
+ binding.floatingActionButton.setOnClickListener {
+ expandFloatingActionButton(!binding.floatingActionButton.isExpanded)
}
- floatingActionButtonDialBackground.setOnClickListener {
+ binding.floatingActionButtonDialBackground.setOnClickListener {
expandFloatingActionButton(false)
}
- addManualButton.setOnClickListener { addManualConsole() }
- addManualLabelButton.setOnClickListener { addManualConsole() }
+ binding.addManualButton.setOnClickListener { addManualConsole() }
+ binding.addManualLabelButton.setOnClickListener { addManualConsole() }
- registerButton.setOnClickListener { showRegistration() }
- registerLabelButton.setOnClickListener { showRegistration() }
+ binding.registerButton.setOnClickListener { showRegistration() }
+ binding.registerLabelButton.setOnClickListener { showRegistration() }
- viewModel = ViewModelProviders
- .of(this, viewModelFactory { MainViewModel(getDatabase(this), Preferences(this)) })
+ viewModel = ViewModelProvider(this, viewModelFactory { MainViewModel(getDatabase(this), Preferences(this)) })
.get(MainViewModel::class.java)
val recyclerViewAdapter = DisplayHostRecyclerViewAdapter(this::hostTriggered, this::wakeupHost, this::editHost, this::deleteHost)
- hostsRecyclerView.adapter = recyclerViewAdapter
- hostsRecyclerView.layoutManager = LinearLayoutManager(this)
+ binding.hostsRecyclerView.adapter = recyclerViewAdapter
+ binding.hostsRecyclerView.layoutManager = LinearLayoutManager(this)
viewModel.displayHosts.observe(this, Observer {
- val top = hostsRecyclerView.computeVerticalScrollOffset() == 0
+ val top = binding.hostsRecyclerView.computeVerticalScrollOffset() == 0
recyclerViewAdapter.hosts = it
if(top)
- hostsRecyclerView.scrollToPosition(0)
+ binding.hostsRecyclerView.scrollToPosition(0)
updateEmptyInfo()
})
@@ -92,19 +78,19 @@ class MainActivity : AppCompatActivity()
{
if(viewModel.displayHosts.value?.isEmpty() ?: true)
{
- emptyInfoLayout.visibility = View.VISIBLE
+ binding.emptyInfoLayout.visibility = View.VISIBLE
val discoveryActive = viewModel.discoveryActive.value ?: false
- emptyInfoImageView.setImageResource(if(discoveryActive) R.drawable.ic_discover_on else R.drawable.ic_discover_off)
- emptyInfoTextView.setText(if(discoveryActive) R.string.display_hosts_empty_discovery_on_info else R.string.display_hosts_empty_discovery_off_info)
+ binding.emptyInfoImageView.setImageResource(if(discoveryActive) R.drawable.ic_discover_on else R.drawable.ic_discover_off)
+ binding.emptyInfoTextView.setText(if(discoveryActive) R.string.display_hosts_empty_discovery_on_info else R.string.display_hosts_empty_discovery_off_info)
}
else
- emptyInfoLayout.visibility = View.GONE
+ binding.emptyInfoLayout.visibility = View.GONE
}
private fun expandFloatingActionButton(expand: Boolean)
{
- floatingActionButton.isExpanded = expand
- floatingActionButton.isActivated = floatingActionButton.isExpanded
+ binding.floatingActionButton.isExpanded = expand
+ binding.floatingActionButton.isActivated = binding.floatingActionButton.isExpanded
}
override fun onStart()
@@ -121,7 +107,7 @@ class MainActivity : AppCompatActivity()
override fun onBackPressed()
{
- if(floatingActionButton.isExpanded)
+ if(binding.floatingActionButton.isExpanded)
{
expandFloatingActionButton(false)
return
@@ -167,7 +153,7 @@ class MainActivity : AppCompatActivity()
private fun addManualConsole()
{
Intent(this, EditManualConsoleActivity::class.java).also {
- it.putRevealExtra(addManualButton, rootLayout)
+ it.putRevealExtra(binding.addManualButton, binding.rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
}
}
@@ -175,7 +161,7 @@ class MainActivity : AppCompatActivity()
private fun showRegistration()
{
Intent(this, RegistActivity::class.java).also {
- it.putRevealExtra(registerButton, rootLayout)
+ it.putRevealExtra(binding.registerButton, binding.rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
}
}
@@ -186,7 +172,7 @@ class MainActivity : AppCompatActivity()
if(registeredHost != null)
{
fun connect() {
- val connectInfo = ConnectInfo(host.host, registeredHost.rpRegistKey, registeredHost.rpKey, Preferences(this).videoProfile)
+ val connectInfo = ConnectInfo(host.isPS5, host.host, registeredHost.rpRegistKey, registeredHost.rpKey, Preferences(this).videoProfile)
Intent(this, StreamActivity::class.java).let {
it.putExtra(StreamActivity.EXTRA_CONNECT_INFO, connectInfo)
startActivity(it)
@@ -225,7 +211,7 @@ class MainActivity : AppCompatActivity()
private fun wakeupHost(host: DisplayHost)
{
val registeredHost = host.registeredHost ?: return
- viewModel.discoveryManager.sendWakeup(host.host, registeredHost.rpRegistKey)
+ viewModel.discoveryManager.sendWakeup(host.host, registeredHost.rpRegistKey, registeredHost.target.isPS5)
}
private fun editHost(host: DisplayHost)
diff --git a/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt
index 02940bc..b4fe9c8 100644
--- a/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.main
@@ -21,7 +6,7 @@ import androidx.lifecycle.ViewModel
import com.metallic.chiaki.common.*
import com.metallic.chiaki.common.ext.toLiveData
import com.metallic.chiaki.discovery.DiscoveryManager
-import com.metallic.chiaki.discovery.ps4Mac
+import com.metallic.chiaki.discovery.serverMac
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
@@ -46,10 +31,10 @@ class MainViewModel(val database: AppDatabase, val preferences: Preferences): Vi
database.registeredHostDao().getAll().toObservable(),
discoveryManager.discoveredHosts)
{ manualHosts, registeredHosts, discoveredHosts ->
- val macRegisteredHosts = registeredHosts.associateBy { it.ps4Mac }
+ val macRegisteredHosts = registeredHosts.associateBy { it.serverMac }
val idRegisteredHosts = registeredHosts.associateBy { it.id }
discoveredHosts.map {
- DiscoveredDisplayHost(it.ps4Mac?.let { mac -> macRegisteredHosts[mac] }, it)
+ DiscoveredDisplayHost(it.serverMac?.let { mac -> macRegisteredHosts[mac] }, it)
} +
manualHosts.map {
ManualDisplayHost(it.registeredHost?.let { id -> idRegisteredHosts[id] }, it)
diff --git a/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleActivity.kt b/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleActivity.kt
index 01ed245..bb64b87 100644
--- a/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.manualconsole
@@ -25,16 +10,16 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import com.metallic.chiaki.R
import com.metallic.chiaki.common.RegisteredHost
import com.metallic.chiaki.common.ext.RevealActivity
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase
+import com.metallic.chiaki.databinding.ActivityEditManualBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
-import kotlinx.android.synthetic.main.activity_edit_manual.*
class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
{
@@ -43,22 +28,23 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
const val EXTRA_MANUAL_HOST_ID = "manual_host_id"
}
- override val revealIntent: Intent get() = intent
- override val revealRootLayout: View get() = rootLayout
- override val revealWindow: Window get() = window
-
private lateinit var viewModel: EditManualConsoleViewModel
+ private lateinit var binding: ActivityEditManualBinding
+
+ override val revealIntent: Intent get() = intent
+ override val revealRootLayout: View get() = binding.rootLayout
+ override val revealWindow: Window get() = window
private val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_edit_manual)
+ binding = ActivityEditManualBinding.inflate(layoutInflater)
+ setContentView(binding.root)
handleReveal()
- viewModel = ViewModelProviders
- .of(this, viewModelFactory {
+ viewModel = ViewModelProvider(this, viewModelFactory {
EditManualConsoleViewModel(getDatabase(this),
if(intent.hasExtra(EXTRA_MANUAL_HOST_ID))
intent.getLongExtra(EXTRA_MANUAL_HOST_ID, 0)
@@ -68,17 +54,17 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
.get(EditManualConsoleViewModel::class.java)
viewModel.existingHost?.observe(this, Observer {
- hostEditText.setText(it.host)
+ binding.hostEditText.setText(it.host)
})
viewModel.selectedRegisteredHost.observe(this, Observer {
- registeredHostTextView.setText(titleForRegisteredHost(it))
+ binding.registeredHostTextView.setText(titleForRegisteredHost(it))
})
viewModel.registeredHosts.observe(this, Observer { hosts ->
- registeredHostTextView.setAdapter(ArrayAdapter(this, R.layout.dropdown_menu_popup_item,
+ binding.registeredHostTextView.setAdapter(ArrayAdapter(this, R.layout.dropdown_menu_popup_item,
hosts.map { titleForRegisteredHost(it) }))
- registeredHostTextView.onItemClickListener = object: AdapterView.OnItemClickListener {
+ binding.registeredHostTextView.onItemClickListener = object: AdapterView.OnItemClickListener {
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long)
{
if(position >= hosts.size)
@@ -89,26 +75,25 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
}
})
-
- saveButton.setOnClickListener { saveHost() }
+ binding.saveButton.setOnClickListener { saveHost() }
}
private fun titleForRegisteredHost(registeredHost: RegisteredHost?) =
if(registeredHost == null)
getString(R.string.add_manual_regist_on_connect)
else
- "${registeredHost.ps4Nickname ?: ""} (${registeredHost.ps4Mac})"
+ "${registeredHost.serverNickname ?: ""} (${registeredHost.serverMac})"
private fun saveHost()
{
- val host = hostEditText.text.toString().trim()
+ val host = binding.hostEditText.text.toString().trim()
if(host.isEmpty())
{
- hostEditText.error = getString(R.string.entered_host_invalid)
+ binding.hostEditText.error = getString(R.string.entered_host_invalid)
return
}
- saveButton.isEnabled = false
+ binding.saveButton.isEnabled = false
viewModel.saveHost(host)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
diff --git a/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleViewModel.kt
index 512e153..160e4a3 100644
--- a/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/manualconsole/EditManualConsoleViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.manualconsole
diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/ChiakiRxLog.kt b/android/app/src/main/java/com/metallic/chiaki/regist/ChiakiRxLog.kt
index 220ac00..f43695f 100644
--- a/android/app/src/main/java/com/metallic/chiaki/regist/ChiakiRxLog.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/regist/ChiakiRxLog.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.regist
diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt b/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt
index aa8b650..9190f9c 100644
--- a/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.regist
@@ -24,11 +9,12 @@ import android.view.View
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import com.metallic.chiaki.R
import com.metallic.chiaki.common.ext.RevealActivity
+import com.metallic.chiaki.databinding.ActivityRegistBinding
import com.metallic.chiaki.lib.RegistInfo
-import kotlinx.android.synthetic.main.activity_regist.*
+import com.metallic.chiaki.lib.Target
import java.lang.IllegalArgumentException
class RegistActivity: AppCompatActivity(), RevealActivity
@@ -44,90 +30,106 @@ class RegistActivity: AppCompatActivity(), RevealActivity
private const val REQUEST_REGIST = 1
}
+ private lateinit var viewModel: RegistViewModel
+ private lateinit var binding: ActivityRegistBinding
+
override val revealWindow: Window get() = window
override val revealIntent: Intent get() = intent
- override val revealRootLayout: View get() = rootLayout
-
- private lateinit var viewModel: RegistViewModel
+ override val revealRootLayout: View get() = binding.rootLayout
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_regist)
+ binding = ActivityRegistBinding.inflate(layoutInflater)
+ setContentView(binding.root)
handleReveal()
- viewModel = ViewModelProviders.of(this).get(RegistViewModel::class.java)
+ viewModel = ViewModelProvider(this).get(RegistViewModel::class.java)
- hostEditText.setText(intent.getStringExtra(EXTRA_HOST) ?: "255.255.255.255")
- broadcastCheckBox.isChecked = intent.getBooleanExtra(EXTRA_BROADCAST, true)
+ binding.hostEditText.setText(intent.getStringExtra(EXTRA_HOST) ?: "255.255.255.255")
+ binding.broadcastCheckBox.isChecked = intent.getBooleanExtra(EXTRA_BROADCAST, true)
- registButton.setOnClickListener { doRegist() }
+ binding.registButton.setOnClickListener { doRegist() }
- ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.PS4Version.GE_7) {
- RegistViewModel.PS4Version.GE_7 -> R.id.ps4VersionGE7RadioButton
- RegistViewModel.PS4Version.LT_7 -> R.id.ps4VersionLT7RadioButton
+ binding.ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5) {
+ RegistViewModel.ConsoleVersion.PS5 -> R.id.ps5RadioButton
+ RegistViewModel.ConsoleVersion.PS4_GE_8 -> R.id.ps4VersionGE8RadioButton
+ RegistViewModel.ConsoleVersion.PS4_GE_7 -> R.id.ps4VersionGE7RadioButton
+ RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.id.ps4VersionLT7RadioButton
})
- ps4VersionRadioGroup.setOnCheckedChangeListener { _, checkedId ->
+ binding.ps4VersionRadioGroup.setOnCheckedChangeListener { _, checkedId ->
viewModel.ps4Version.value = when(checkedId)
{
- R.id.ps4VersionGE7RadioButton -> RegistViewModel.PS4Version.GE_7
- R.id.ps4VersionLT7RadioButton -> RegistViewModel.PS4Version.LT_7
- else -> RegistViewModel.PS4Version.GE_7
+ R.id.ps5RadioButton -> RegistViewModel.ConsoleVersion.PS5
+ R.id.ps4VersionGE8RadioButton -> RegistViewModel.ConsoleVersion.PS4_GE_8
+ R.id.ps4VersionGE7RadioButton -> RegistViewModel.ConsoleVersion.PS4_GE_7
+ R.id.ps4VersionLT7RadioButton -> RegistViewModel.ConsoleVersion.PS4_LT_7
+ else -> RegistViewModel.ConsoleVersion.PS5
}
}
viewModel.ps4Version.observe(this, Observer {
- psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.PS4Version.GE_7) View.VISIBLE else View.GONE
- psnIdTextInputLayout.hint = getString(when(it!!)
+ binding.psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.ConsoleVersion.PS4_LT_7) View.GONE else View.VISIBLE
+ binding.psnIdTextInputLayout.hint = getString(when(it!!)
{
- RegistViewModel.PS4Version.GE_7 -> R.string.hint_regist_psn_account_id
- RegistViewModel.PS4Version.LT_7 -> R.string.hint_regist_psn_online_id
+ RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.hint_regist_psn_online_id
+ else -> R.string.hint_regist_psn_account_id
})
+ binding.pinHelpBeforeTextView.setText(if(it.isPS5) R.string.regist_pin_instructions_ps5_before else R.string.regist_pin_instructions_ps4_before)
+ binding.pinHelpNavigationTextView.setText(if(it.isPS5) R.string.regist_pin_instructions_ps5_navigation else R.string.regist_pin_instructions_ps4_navigation)
})
}
private fun doRegist()
{
- val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.PS4Version.GE_7
+ val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5
- val host = hostEditText.text.toString().trim()
+ val host = binding.hostEditText.text.toString().trim()
val hostValid = host.isNotEmpty()
- val broadcast = broadcastCheckBox.isChecked
+ val broadcast = binding.broadcastCheckBox.isChecked
- val psnId = psnIdEditText.text.toString().trim()
- val psnOnlineId: String? = if(ps4Version == RegistViewModel.PS4Version.LT_7) psnId else null
+ val psnId = binding.psnIdEditText.text.toString().trim()
+ val psnOnlineId: String? = if(ps4Version == RegistViewModel.ConsoleVersion.PS4_LT_7) psnId else null
val psnAccountId: ByteArray? =
- if(ps4Version == RegistViewModel.PS4Version.GE_7)
+ if(ps4Version != RegistViewModel.ConsoleVersion.PS4_LT_7)
try { Base64.decode(psnId, Base64.DEFAULT) } catch(e: IllegalArgumentException) { null }
else
null
val psnIdValid = when(ps4Version)
{
- RegistViewModel.PS4Version.GE_7 -> psnAccountId != null && psnAccountId.size == RegistInfo.ACCOUNT_ID_SIZE
- RegistViewModel.PS4Version.LT_7 -> psnOnlineId?.isNotEmpty() ?: false
+ RegistViewModel.ConsoleVersion.PS4_LT_7 -> psnOnlineId?.isNotEmpty() ?: false
+ else -> psnAccountId != null && psnAccountId.size == RegistInfo.ACCOUNT_ID_SIZE
}
- val pin = pinEditText.text.toString()
+ val pin = binding.pinEditText.text.toString()
val pinValid = pin.length == PIN_LENGTH
- hostEditText.error = if(!hostValid) getString(R.string.entered_host_invalid) else null
- psnIdEditText.error =
+ binding.hostEditText.error = if(!hostValid) getString(R.string.entered_host_invalid) else null
+ binding.psnIdEditText.error =
if(!psnIdValid)
getString(when(ps4Version)
{
- RegistViewModel.PS4Version.GE_7 -> R.string.regist_psn_account_id_invalid
- RegistViewModel.PS4Version.LT_7 -> R.string.regist_psn_online_id_invalid
+ RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.regist_psn_online_id_invalid
+ else -> R.string.regist_psn_account_id_invalid
})
else
null
- pinEditText.error = if(!pinValid) getString(R.string.regist_pin_invalid, PIN_LENGTH) else null
+ binding.pinEditText.error = if(!pinValid) getString(R.string.regist_pin_invalid, PIN_LENGTH) else null
if(!hostValid || !psnIdValid || !pinValid)
return
- val registInfo = RegistInfo(host, broadcast, psnOnlineId, psnAccountId, pin.toInt())
+ val target = when(ps4Version)
+ {
+ RegistViewModel.ConsoleVersion.PS5 -> Target.PS5_1
+ RegistViewModel.ConsoleVersion.PS4_GE_8 -> Target.PS4_10
+ RegistViewModel.ConsoleVersion.PS4_GE_7 -> Target.PS4_9
+ RegistViewModel.ConsoleVersion.PS4_LT_7 -> Target.PS4_8
+ }
+
+ val registInfo = RegistInfo(target, host, broadcast, psnOnlineId, psnAccountId, pin.toInt())
Intent(this, RegistExecuteActivity::class.java).also {
it.putExtra(RegistExecuteActivity.EXTRA_REGIST_INFO, registInfo)
diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteActivity.kt b/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteActivity.kt
index 4136248..16f545a 100644
--- a/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.regist
@@ -25,14 +10,14 @@ import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.metallic.chiaki.R
import com.metallic.chiaki.common.MacAddress
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase
+import com.metallic.chiaki.databinding.ActivityRegistExecuteBinding
import com.metallic.chiaki.lib.RegistInfo
-import kotlinx.android.synthetic.main.activity_regist_execute.*
import kotlin.math.max
class RegistExecuteActivity: AppCompatActivity()
@@ -46,52 +31,57 @@ class RegistExecuteActivity: AppCompatActivity()
}
private lateinit var viewModel: RegistExecuteViewModel
+ private lateinit var binding: ActivityRegistExecuteBinding
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_regist_execute)
+ binding = ActivityRegistExecuteBinding.inflate(layoutInflater)
+ setContentView(binding.root)
- viewModel = ViewModelProviders
- .of(this, viewModelFactory { RegistExecuteViewModel(getDatabase(this)) })
+ viewModel = ViewModelProvider(this, viewModelFactory { RegistExecuteViewModel(getDatabase(this)) })
.get(RegistExecuteViewModel::class.java)
- logTextView.setHorizontallyScrolling(true)
- logTextView.movementMethod = ScrollingMovementMethod()
+ binding.logTextView.setHorizontallyScrolling(true)
+ binding.logTextView.movementMethod = ScrollingMovementMethod()
viewModel.logText.observe(this, Observer {
- logTextView.text = it
- val scrollY = logTextView.layout.getLineBottom(logTextView.lineCount - 1) - logTextView.height + logTextView.paddingTop + logTextView.paddingBottom
- logTextView.scrollTo(0, max(scrollY, 0))
+ val textLayout = binding.logTextView.layout ?: return@Observer
+ val lineCount = textLayout.lineCount
+ if(lineCount < 1)
+ return@Observer
+ binding.logTextView.text = it
+ val scrollY = textLayout.getLineBottom(lineCount - 1) - binding.logTextView.height + binding.logTextView.paddingTop + binding.logTextView.paddingBottom
+ binding.logTextView.scrollTo(0, max(scrollY, 0))
})
viewModel.state.observe(this, Observer {
- progressBar.visibility = if(it == RegistExecuteViewModel.State.RUNNING) View.VISIBLE else View.GONE
+ binding.progressBar.visibility = if(it == RegistExecuteViewModel.State.RUNNING) View.VISIBLE else View.GONE
when(it)
{
RegistExecuteViewModel.State.FAILED ->
{
- infoTextView.visibility = View.VISIBLE
- infoTextView.setText(R.string.regist_info_failed)
+ binding.infoTextView.visibility = View.VISIBLE
+ binding.infoTextView.setText(R.string.regist_info_failed)
setResult(RESULT_FAILED)
}
RegistExecuteViewModel.State.SUCCESSFUL, RegistExecuteViewModel.State.SUCCESSFUL_DUPLICATE ->
{
- infoTextView.visibility = View.VISIBLE
- infoTextView.setText(R.string.regist_info_success)
+ binding.infoTextView.visibility = View.VISIBLE
+ binding.infoTextView.setText(R.string.regist_info_success)
setResult(RESULT_OK)
if(it == RegistExecuteViewModel.State.SUCCESSFUL_DUPLICATE)
showDuplicateDialog()
}
RegistExecuteViewModel.State.STOPPED ->
{
- infoTextView.visibility = View.GONE
+ binding.infoTextView.visibility = View.GONE
setResult(Activity.RESULT_CANCELED)
}
- else -> infoTextView.visibility = View.GONE
+ else -> binding.infoTextView.visibility = View.GONE
}
})
- shareLogButton.setOnClickListener {
+ binding.shareLogButton.setOnClickListener {
val log = viewModel.logText.value ?: ""
Intent(Intent.ACTION_SEND).also {
it.type = "text/plain"
@@ -126,7 +116,7 @@ class RegistExecuteActivity: AppCompatActivity()
if(dialog != null)
return
- val macStr = viewModel.host?.ps4Mac?.let { MacAddress(it).toString() } ?: ""
+ val macStr = viewModel.host?.serverMac?.let { MacAddress(it).toString() } ?: ""
dialog = MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.alert_regist_duplicate, macStr))
diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteViewModel.kt
index 8be8343..822fb8e 100644
--- a/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/regist/RegistExecuteViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.regist
@@ -93,7 +78,7 @@ class RegistExecuteViewModel(val database: AppDatabase): ViewModel()
private fun registSuccess(host: RegistHost)
{
this.host = host
- database.registeredHostDao().getByMac(MacAddress(host.ps4Mac))
+ database.registeredHostDao().getByMac(MacAddress(host.serverMac))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess {
@@ -113,7 +98,7 @@ class RegistExecuteViewModel(val database: AppDatabase): ViewModel()
val dao = database.registeredHostDao()
val manualHostDao = database.manualHostDao()
val registeredHost = RegisteredHost(host)
- dao.deleteByMac(registeredHost.ps4Mac)
+ dao.deleteByMac(registeredHost.serverMac)
.andThen(dao.insert(registeredHost))
.let {
if(assignManualHostId != null)
diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt
index 873008e..ab7253f 100644
--- a/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.regist
@@ -22,10 +7,14 @@ import androidx.lifecycle.ViewModel
class RegistViewModel: ViewModel()
{
- enum class PS4Version {
- GE_7,
- LT_7
+ enum class ConsoleVersion {
+ PS5,
+ PS4_GE_8,
+ PS4_GE_7,
+ PS4_LT_7;
+
+ val isPS5 get() = this == PS5
}
- val ps4Version = MutableLiveData(PS4Version.GE_7)
+ val ps4Version = MutableLiveData(ConsoleVersion.PS5)
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt b/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt
index e96b7b5..822fe76 100644
--- a/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt
@@ -1,19 +1,37 @@
package com.metallic.chiaki.session
-import android.util.Log
-import android.view.InputDevice
-import android.view.KeyEvent
-import android.view.MotionEvent
+import android.content.Context
+import android.hardware.*
+import android.view.*
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.OnLifecycleEvent
import com.metallic.chiaki.common.Preferences
import com.metallic.chiaki.lib.ControllerState
-class StreamInput(val preferences: Preferences)
+class StreamInput(val context: Context, val preferences: Preferences)
{
var controllerStateChangedCallback: ((ControllerState) -> Unit)? = null
val controllerState: ControllerState get()
{
- val controllerState = keyControllerState or motionControllerState
+ val controllerState = sensorControllerState or keyControllerState or motionControllerState
+
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ @Suppress("DEPRECATION")
+ when(windowManager.defaultDisplay.rotation)
+ {
+ Surface.ROTATION_90 -> {
+ controllerState.accelX *= -1.0f
+ controllerState.accelZ *= -1.0f
+ controllerState.gyroX *= -1.0f
+ controllerState.gyroZ *= -1.0f
+ controllerState.orientX *= -1.0f
+ controllerState.orientZ *= -1.0f
+ }
+ else -> {}
+ }
// prioritize motion controller's l2 and r2 over key
// (some controllers send only key, others both but key earlier than full press)
@@ -25,6 +43,7 @@ class StreamInput(val preferences: Preferences)
return controllerState or touchControllerState
}
+ private val sensorControllerState = ControllerState() // from Motion Sensors
private val keyControllerState = ControllerState() // from KeyEvents
private val motionControllerState = ControllerState() // from MotionEvents
var touchControllerState = ControllerState()
@@ -36,6 +55,66 @@ class StreamInput(val preferences: Preferences)
private val swapCrossMoon = preferences.swapCrossMoon
+ private val sensorEventListener = object: SensorEventListener {
+ override fun onSensorChanged(event: SensorEvent)
+ {
+ when(event.sensor.type)
+ {
+ Sensor.TYPE_ACCELEROMETER -> {
+ sensorControllerState.accelX = event.values[1] / SensorManager.GRAVITY_EARTH
+ sensorControllerState.accelY = event.values[2] / SensorManager.GRAVITY_EARTH
+ sensorControllerState.accelZ = event.values[0] / SensorManager.GRAVITY_EARTH
+ }
+ Sensor.TYPE_GYROSCOPE -> {
+ sensorControllerState.gyroX = event.values[1]
+ sensorControllerState.gyroY = event.values[2]
+ sensorControllerState.gyroZ = event.values[0]
+ }
+ Sensor.TYPE_ROTATION_VECTOR -> {
+ val q = floatArrayOf(0f, 0f, 0f, 0f)
+ SensorManager.getQuaternionFromVector(q, event.values)
+ sensorControllerState.orientX = q[2]
+ sensorControllerState.orientY = q[3]
+ sensorControllerState.orientZ = q[1]
+ sensorControllerState.orientW = q[0]
+ }
+ else -> return
+ }
+ controllerStateUpdated()
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
+ }
+
+ private val motionLifecycleObserver = object: LifecycleObserver {
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ fun onResume()
+ {
+ val samplingPeriodUs = 4000
+ val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ listOfNotNull(
+ sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+ sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
+ sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
+ ).forEach {
+ sensorManager.registerListener(sensorEventListener, it, samplingPeriodUs)
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ fun onPause()
+ {
+ val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ sensorManager.unregisterListener(sensorEventListener)
+ }
+ }
+
+ fun observe(lifecycleOwner: LifecycleOwner)
+ {
+ if(preferences.motionEnabled)
+ lifecycleOwner.lifecycle.addObserver(motionLifecycleObserver)
+ }
+
private fun controllerStateUpdated()
{
controllerStateChangedCallback?.let { it(controllerState) }
@@ -98,7 +177,7 @@ class StreamInput(val preferences: Preferences)
{
if(event.source and InputDevice.SOURCE_CLASS_JOYSTICK != InputDevice.SOURCE_CLASS_JOYSTICK)
return false
- fun Float.signedAxis() = (this * Short.MAX_VALUE).toShort()
+ fun Float.signedAxis() = (this * Short.MAX_VALUE).toInt().toShort()
fun Float.unsignedAxis() = (this * UByte.MAX_VALUE.toFloat()).toUInt().toUByte()
motionControllerState.leftX = event.getAxisValue(MotionEvent.AXIS_X).signedAxis()
motionControllerState.leftY = event.getAxisValue(MotionEvent.AXIS_Y).signedAxis()
diff --git a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt
index aaf70aa..dd32a40 100644
--- a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.session
@@ -40,8 +25,11 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
private val _state = MutableLiveData(StreamStateIdle)
val state: LiveData get() = _state
+ private val _rumbleState = MutableLiveData(RumbleEvent(0U, 0U))
+ val rumbleState: LiveData get() = _rumbleState
- var surfaceTexture: SurfaceTexture? = null
+ private var surfaceTexture: SurfaceTexture? = null
+ private var surface: Surface? = null
init
{
@@ -74,9 +62,9 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
_state.value = StreamStateConnecting
session.eventCallback = this::eventCallback
session.start()
- val surfaceTexture = surfaceTexture
- if(surfaceTexture != null)
- session.setSurface(Surface(surfaceTexture))
+ val surface = surface
+ if(surface != null)
+ session.setSurface(surface)
this.session = session
}
catch(e: CreateError)
@@ -101,9 +89,30 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
event.pinIncorrect
)
)
+ is RumbleEvent -> _rumbleState.postValue(event)
}
}
+ fun attachToSurfaceView(surfaceView: SurfaceView)
+ {
+ surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
+ override fun surfaceCreated(holder: SurfaceHolder) { }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int)
+ {
+ val surface = holder.surface
+ this@StreamSession.surface = surface
+ session?.setSurface(surface)
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder)
+ {
+ this@StreamSession.surface = null
+ session?.setSurface(null)
+ }
+ })
+ }
+
fun attachToTextureView(textureView: TextureView)
{
textureView.surfaceTextureListener = object: TextureView.SurfaceTextureListener {
@@ -112,6 +121,7 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
if(surfaceTexture != null)
return
surfaceTexture = surface
+ this@StreamSession.surface = Surface(surfaceTexture)
session?.setSurface(Surface(surface))
}
@@ -127,7 +137,7 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
val surfaceTexture = surfaceTexture
if(surfaceTexture != null)
- textureView.surfaceTexture = surfaceTexture
+ textureView.setSurfaceTexture(surfaceTexture)
}
fun setLoginPin(pin: String)
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/ItemTouchSwipeCallback.kt b/android/app/src/main/java/com/metallic/chiaki/settings/ItemTouchSwipeCallback.kt
index 1d411a9..7168bce 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/ItemTouchSwipeCallback.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/ItemTouchSwipeCallback.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsActivity.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsActivity.kt
index 7ac9e24..1cf224f 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
@@ -24,7 +9,7 @@ import androidx.fragment.app.Fragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.metallic.chiaki.R
-import kotlinx.android.synthetic.main.activity_settings.*
+import com.metallic.chiaki.databinding.ActivitySettingsBinding
interface TitleFragment
{
@@ -33,20 +18,23 @@ interface TitleFragment
class SettingsActivity: AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
{
+ private lateinit var binding: ActivitySettingsBinding
+
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_settings)
+ binding = ActivitySettingsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
title = ""
- setSupportActionBar(toolbar)
+ setSupportActionBar(binding.toolbar)
val rootFragment = SettingsFragment()
replaceFragment(rootFragment, false)
supportFragmentManager.addOnBackStackChangedListener {
val titleFragment = supportFragmentManager.findFragmentById(R.id.settingsFragment) as? TitleFragment ?: return@addOnBackStackChangedListener
- titleTextView.text = titleFragment.getTitle(resources)
+ binding.titleTextView.text = titleFragment.getTitle(resources)
}
- titleTextView.text = rootFragment.getTitle(resources)
+ binding.titleTextView.text = rootFragment.getTitle(resources)
}
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference) = when(pref.fragment)
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt
index c3adf7b..f574d09 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt
@@ -1,33 +1,23 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
+import android.app.Activity
+import android.content.Intent
import android.content.res.Resources
import android.os.Bundle
import android.text.InputType
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import androidx.preference.*
import com.metallic.chiaki.R
import com.metallic.chiaki.common.Preferences
-import com.metallic.chiaki.common.ext.toLiveData
+import com.metallic.chiaki.common.exportAndShareAllSettings
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase
+import com.metallic.chiaki.common.importSettingsFromUri
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxkotlin.addTo
class DataStore(val preferences: Preferences): PreferenceDataStore()
{
@@ -35,6 +25,9 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
{
preferences.logVerboseKey -> preferences.logVerbose
preferences.swapCrossMoonKey -> preferences.swapCrossMoon
+ preferences.rumbleEnabledKey -> preferences.rumbleEnabled
+ preferences.motionEnabledKey -> preferences.motionEnabled
+ preferences.buttonHapticEnabledKey -> preferences.buttonHapticEnabled
else -> defValue
}
@@ -44,6 +37,9 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
{
preferences.logVerboseKey -> preferences.logVerbose = value
preferences.swapCrossMoonKey -> preferences.swapCrossMoon = value
+ preferences.rumbleEnabledKey -> preferences.rumbleEnabled = value
+ preferences.motionEnabledKey -> preferences.motionEnabled = value
+ preferences.buttonHapticEnabledKey -> preferences.buttonHapticEnabled = value
}
}
@@ -52,6 +48,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
preferences.resolutionKey -> preferences.resolution.value
preferences.fpsKey -> preferences.fps.value
preferences.bitrateKey -> preferences.bitrate?.toString() ?: ""
+ preferences.codecKey -> preferences.codec.value
else -> defValue
}
@@ -70,18 +67,30 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
preferences.fps = fps
}
preferences.bitrateKey -> preferences.bitrate = value?.toIntOrNull()
+ preferences.codecKey ->
+ {
+ val codec = Preferences.Codec.values().firstOrNull { it.value == value } ?: return
+ preferences.codec = codec
+ }
}
}
}
class SettingsFragment: PreferenceFragmentCompat(), TitleFragment
{
+ companion object
+ {
+ private const val PICK_SETTINGS_JSON_REQUEST = 1
+ }
+
+ private var disposable = CompositeDisposable()
+ private var exportDisposable = CompositeDisposable().also { it.addTo(disposable) }
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?)
{
val context = context ?: return
- val viewModel = ViewModelProviders
- .of(this, viewModelFactory { SettingsViewModel(getDatabase(context), Preferences(context)) })
+ val viewModel = ViewModelProvider(this, viewModelFactory { SettingsViewModel(getDatabase(context), Preferences(context)) })
.get(SettingsViewModel::class.java)
val preferences = viewModel.preferences
@@ -114,11 +123,52 @@ class SettingsFragment: PreferenceFragmentCompat(), TitleFragment
bitratePreference?.summaryProvider = bitrateSummaryProvider
})
+ preferenceScreen.findPreference(getString(R.string.preferences_codec_key))?.let {
+ it.entryValues = Preferences.codecAll.map { codec -> codec.value }.toTypedArray()
+ it.entries = Preferences.codecAll.map { codec -> getString(codec.title) }.toTypedArray()
+ }
+
val registeredHostsPreference = preferenceScreen.findPreference("registered_hosts")
viewModel.registeredHostsCount.observe(this, Observer {
registeredHostsPreference?.summary = getString(R.string.preferences_registered_hosts_summary, it)
})
+
+ preferenceScreen.findPreference(getString(R.string.preferences_export_settings_key))?.setOnPreferenceClickListener { exportSettings(); true }
+ preferenceScreen.findPreference(getString(R.string.preferences_import_settings_key))?.setOnPreferenceClickListener { importSettings(); true }
+ }
+
+ override fun onDestroy()
+ {
+ super.onDestroy()
+ disposable.dispose()
}
override fun getTitle(resources: Resources): String = resources.getString(R.string.title_settings)
+
+ private fun exportSettings()
+ {
+ val activity = activity ?: return
+ exportDisposable.clear()
+ exportAndShareAllSettings(activity).addTo(exportDisposable)
+ }
+
+ private fun importSettings()
+ {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "application/json"
+ }
+ startActivityForResult(intent, PICK_SETTINGS_JSON_REQUEST)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
+ {
+ if(requestCode == PICK_SETTINGS_JSON_REQUEST && resultCode == Activity.RESULT_OK)
+ {
+ val activity = activity ?: return
+ data?.data?.also {
+ importSettingsFromUri(activity, it, disposable)
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsAdapter.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsAdapter.kt
index 171d329..6acf547 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsAdapter.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsAdapter.kt
@@ -1,29 +1,14 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
-import android.view.View
+import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.metallic.chiaki.R
import com.metallic.chiaki.common.LogFile
import com.metallic.chiaki.common.ext.inflate
-import kotlinx.android.synthetic.main.item_log_file.view.*
+import com.metallic.chiaki.databinding.ItemLogFileBinding
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
@@ -35,7 +20,7 @@ class SettingsLogsAdapter: RecyclerView.Adapter(
private val dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT)
private val timeFormat = SimpleDateFormat("HH:mm:ss:SSS", Locale.getDefault())
- class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+ class ViewHolder(val binding: ItemLogFileBinding): RecyclerView.ViewHolder(binding.root)
var logFiles: List = listOf()
set(value)
@@ -44,16 +29,16 @@ class SettingsLogsAdapter: RecyclerView.Adapter(
notifyDataSetChanged()
}
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(parent.inflate(R.layout.item_log_file))
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+ ViewHolder(ItemLogFileBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = logFiles.size
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
- val view = holder.itemView
val logFile = logFiles[position]
- view.nameTextView.text = "${dateFormat.format(logFile.date)} ${timeFormat.format(logFile.date)}"
- view.summaryTextView.text = logFile.filename
- view.shareButton.setOnClickListener { shareCallback?.let { it(logFile) } }
+ holder.binding.nameTextView.text = "${dateFormat.format(logFile.date)} ${timeFormat.format(logFile.date)}"
+ holder.binding.summaryTextView.text = logFile.filename
+ holder.binding.shareButton.setOnClickListener { shareCallback?.let { it(logFile) } }
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsFragment.kt
index b1edbba..0397172 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsFragment.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsFragment.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
@@ -27,7 +12,7 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.core.content.FileProvider
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -36,30 +21,35 @@ import com.metallic.chiaki.common.LogFile
import com.metallic.chiaki.common.LogManager
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.fileProviderAuthority
-import kotlinx.android.synthetic.main.fragment_settings_logs.*
+import com.metallic.chiaki.databinding.FragmentSettingsLogsBinding
class SettingsLogsFragment: AppCompatDialogFragment(), TitleFragment
{
private lateinit var viewModel: SettingsLogsViewModel
+ private var _binding: FragmentSettingsLogsBinding? = null
+ private val binding get() = _binding!!
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
- inflater.inflate(R.layout.fragment_settings_logs, container, false)
+ FragmentSettingsLogsBinding.inflate(inflater, container, false).let {
+ _binding = it
+ it.root
+ }
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
- val context = context!!
+ val context = requireContext()
- viewModel = ViewModelProviders
- .of(this, viewModelFactory { SettingsLogsViewModel(LogManager(context)) })
+ viewModel = ViewModelProvider(this, viewModelFactory { SettingsLogsViewModel(LogManager(context)) })
.get(SettingsLogsViewModel::class.java)
val adapter = SettingsLogsAdapter()
- logsRecyclerView.layoutManager = LinearLayoutManager(context)
- logsRecyclerView.adapter = adapter
+ binding.logsRecyclerView.layoutManager = LinearLayoutManager(context)
+ binding.logsRecyclerView.adapter = adapter
adapter.shareCallback = this::shareLogFile
- viewModel.sessionLogs.observe(this, Observer {
+ viewModel.sessionLogs.observe(viewLifecycleOwner, Observer {
adapter.logFiles = it
- emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE
+ binding.emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE
})
val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context)
@@ -71,7 +61,7 @@ class SettingsLogsFragment: AppCompatDialogFragment(), TitleFragment
viewModel.deleteLog(file)
}
}
- ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(logsRecyclerView)
+ ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(binding.logsRecyclerView)
}
override fun getTitle(resources: Resources): String = resources.getString(R.string.preferences_logs_title)
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsViewModel.kt
index c2c2a53..89a58de 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsLogsViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsAdapter.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsAdapter.kt
index 7cedf2a..509d594 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsAdapter.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsAdapter.kt
@@ -1,33 +1,16 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
-import android.view.View
+import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.metallic.chiaki.R
import com.metallic.chiaki.common.RegisteredHost
-import com.metallic.chiaki.common.ext.inflate
-import kotlinx.android.synthetic.main.item_registered_host.view.*
+import com.metallic.chiaki.databinding.ItemRegisteredHostBinding
class SettingsRegisteredHostsAdapter: RecyclerView.Adapter()
{
- class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+ class ViewHolder(val binding: ItemRegisteredHostBinding): RecyclerView.ViewHolder(binding.root)
var hosts: List = listOf()
set(value)
@@ -36,15 +19,15 @@ class SettingsRegisteredHostsAdapter: RecyclerView.Adapter.
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
@@ -26,7 +11,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -35,33 +20,39 @@ import com.metallic.chiaki.R
import com.metallic.chiaki.common.ext.putRevealExtra
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase
+import com.metallic.chiaki.databinding.FragmentSettingsRegisteredHostsBinding
import com.metallic.chiaki.regist.RegistActivity
-import kotlinx.android.synthetic.main.fragment_settings_registered_hosts.*
class SettingsRegisteredHostsFragment: AppCompatDialogFragment(), TitleFragment
{
private lateinit var viewModel: SettingsRegisteredHostsViewModel
+ private var _binding: FragmentSettingsRegisteredHostsBinding? = null
+ private val binding get() = _binding!!
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
- inflater.inflate(R.layout.fragment_settings_registered_hosts, container, false)
+ FragmentSettingsRegisteredHostsBinding.inflate(inflater, container, false).let {
+ _binding = it
+ it.root
+ }
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
- viewModel = ViewModelProviders
- .of(this, viewModelFactory { SettingsRegisteredHostsViewModel(getDatabase(context!!)) })
+ val context = requireContext()
+ viewModel = ViewModelProvider(this, viewModelFactory { SettingsRegisteredHostsViewModel(getDatabase(context)) })
.get(SettingsRegisteredHostsViewModel::class.java)
val adapter = SettingsRegisteredHostsAdapter()
- hostsRecyclerView.layoutManager = LinearLayoutManager(context)
- hostsRecyclerView.adapter = adapter
- val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context!!)
+ binding.hostsRecyclerView.layoutManager = LinearLayoutManager(context)
+ binding.hostsRecyclerView.adapter = adapter
+ val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context)
{
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int)
{
val pos = viewHolder.adapterPosition
val host = viewModel.registeredHosts.value?.getOrNull(pos) ?: return
MaterialAlertDialogBuilder(viewHolder.itemView.context)
- .setMessage(getString(R.string.alert_message_delete_registered_host, host.ps4Nickname, host.ps4Mac.toString()))
+ .setMessage(getString(R.string.alert_message_delete_registered_host, host.serverNickname, host.serverMac.toString()))
.setPositiveButton(R.string.action_delete) { _, _ ->
viewModel.deleteHost(host)
}
@@ -72,15 +63,15 @@ class SettingsRegisteredHostsFragment: AppCompatDialogFragment(), TitleFragment
.show()
}
}
- ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(hostsRecyclerView)
+ ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(binding.hostsRecyclerView)
viewModel.registeredHosts.observe(this, Observer {
adapter.hosts = it
- emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE
+ binding.emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE
})
- floatingActionButton.setOnClickListener {
+ binding.floatingActionButton.setOnClickListener {
Intent(context, RegistActivity::class.java).also {
- it.putRevealExtra(floatingActionButton, rootLayout)
+ it.putRevealExtra(binding.floatingActionButton, binding.rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(activity).toBundle())
}
}
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsViewModel.kt
index 418ae4a..a00e897 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsRegisteredHostsViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsViewModel.kt
index e69662a..3df021c 100644
--- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsViewModel.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.settings
diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/AspectRatioFrameLayout.kt b/android/app/src/main/java/com/metallic/chiaki/stream/AspectRatioFrameLayout.kt
new file mode 100644
index 0000000..5394f02
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/stream/AspectRatioFrameLayout.kt
@@ -0,0 +1,68 @@
+package com.metallic.chiaki.stream
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+// see ExoPlayer's AspectRatioFrameLayout
+class AspectRatioFrameLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null): FrameLayout(context, attrs)
+{
+ companion object
+ {
+ private const val MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f
+ }
+
+ var aspectRatio = 0f
+ set(value)
+ {
+ if(field != value)
+ {
+ field = value
+ requestLayout()
+ }
+ }
+
+ var mode: TransformMode = TransformMode.FIT
+ set(value)
+ {
+ if(field != value)
+ {
+ field = value
+ requestLayout()
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
+ {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ if(aspectRatio <= 0)
+ {
+ // Aspect ratio not set.
+ return
+ }
+ var width = measuredWidth
+ var height = measuredHeight
+ val viewAspectRatio = width.toFloat() / height
+ val aspectDeformation = aspectRatio / viewAspectRatio - 1
+ if(Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION)
+ return
+ when(mode)
+ {
+ TransformMode.ZOOM ->
+ if(aspectDeformation > 0)
+ width = (height * aspectRatio).toInt()
+ else
+ height = (width / aspectRatio).toInt()
+ TransformMode.FIT ->
+ if(aspectDeformation > 0)
+ height = (width / aspectRatio).toInt()
+ else
+ width = (height * aspectRatio).toInt()
+ TransformMode.STRETCH -> {}
+ }
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
+ )
+ }
+}
diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt
index 43dee24..bd77e0c 100644
--- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.stream
@@ -21,11 +6,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.AlertDialog
import android.graphics.Matrix
-import android.os.Bundle
-import android.os.Handler
-import android.view.KeyEvent
-import android.view.MotionEvent
-import android.view.View
+import android.os.*
+import android.view.*
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
@@ -34,13 +16,18 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.metallic.chiaki.R
-import com.metallic.chiaki.common.LogManager
import com.metallic.chiaki.common.Preferences
import com.metallic.chiaki.common.ext.viewModelFactory
+import com.metallic.chiaki.databinding.ActivityStreamBinding
import com.metallic.chiaki.lib.ConnectInfo
+import com.metallic.chiaki.lib.ConnectVideoProfile
import com.metallic.chiaki.session.*
+import com.metallic.chiaki.touchcontrols.DefaultTouchControlsFragment
import com.metallic.chiaki.touchcontrols.TouchControlsFragment
-import kotlinx.android.synthetic.main.activity_stream.*
+import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxkotlin.addTo
+import kotlin.math.min
private sealed class DialogContents
private object StreamQuitDialog: DialogContents()
@@ -56,6 +43,8 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
}
private lateinit var viewModel: StreamViewModel
+ private lateinit var binding: ActivityStreamBinding
+
private val uiVisibilityHandler = Handler()
override fun onCreate(savedInstanceState: Bundle?)
@@ -69,36 +58,77 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
return
}
- viewModel = ViewModelProviders.of(this, viewModelFactory {
- StreamViewModel(Preferences(this), LogManager(this), connectInfo)
+ viewModel = ViewModelProvider(this, viewModelFactory {
+ StreamViewModel(application, connectInfo)
})[StreamViewModel::class.java]
- setContentView(R.layout.activity_stream)
+ viewModel.input.observe(this)
+
+ binding = ActivityStreamBinding.inflate(layoutInflater)
+ setContentView(binding.root)
window.decorView.setOnSystemUiVisibilityChangeListener(this)
viewModel.onScreenControlsEnabled.observe(this, Observer {
- if(onScreenControlsSwitch.isChecked != it)
- onScreenControlsSwitch.isChecked = it
+ if(binding.onScreenControlsSwitch.isChecked != it)
+ binding.onScreenControlsSwitch.isChecked = it
+ if(binding.onScreenControlsSwitch.isChecked)
+ binding.touchpadOnlySwitch.isChecked = false
})
- onScreenControlsSwitch.setOnCheckedChangeListener { _, isChecked ->
+ binding.onScreenControlsSwitch.setOnCheckedChangeListener { _, isChecked ->
viewModel.setOnScreenControlsEnabled(isChecked)
showOverlay()
}
- viewModel.session.attachToTextureView(textureView)
+ viewModel.touchpadOnlyEnabled.observe(this, Observer {
+ if(binding.touchpadOnlySwitch.isChecked != it)
+ binding.touchpadOnlySwitch.isChecked = it
+ if(binding.touchpadOnlySwitch.isChecked)
+ binding.onScreenControlsSwitch.isChecked = false
+ })
+ binding.touchpadOnlySwitch.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.setTouchpadOnlyEnabled(isChecked)
+ showOverlay()
+ }
+
+ binding.displayModeToggle.addOnButtonCheckedListener { _, _, _ ->
+ adjustStreamViewAspect()
+ showOverlay()
+ }
+
+ //viewModel.session.attachToTextureView(textureView)
+ viewModel.session.attachToSurfaceView(binding.surfaceView)
viewModel.session.state.observe(this, Observer { this.stateChanged(it) })
- textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- adjustTextureViewAspect()
+ adjustStreamViewAspect()
+
+ if(Preferences(this).rumbleEnabled)
+ {
+ val vibrator = getSystemService(VIBRATOR_SERVICE) as Vibrator
+ viewModel.session.rumbleState.observe(this, Observer {
+ val amplitude = min(255, (it.left.toInt() + it.right.toInt()) / 2)
+ vibrator.cancel()
+ if(amplitude == 0)
+ return@Observer
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ vibrator.vibrate(VibrationEffect.createOneShot(1000, amplitude))
+ else
+ vibrator.vibrate(1000)
+ })
}
}
+ private val controlsDisposable = CompositeDisposable()
+
override fun onAttachFragment(fragment: Fragment)
{
super.onAttachFragment(fragment)
if(fragment is TouchControlsFragment)
{
- fragment.controllerStateCallback = { viewModel.input.touchControllerState = it }
+ fragment.controllerState
+ .subscribe { viewModel.input.touchControllerState = it }
+ .addTo(controlsDisposable)
fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled
+ if(fragment is TouchpadOnlyFragment)
+ fragment.touchpadOnlyEnabled = viewModel.touchpadOnlyEnabled
}
}
@@ -115,6 +145,12 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
viewModel.session.pause()
}
+ override fun onDestroy()
+ {
+ super.onDestroy()
+ controlsDisposable.dispose()
+ }
+
private fun reconnect()
{
viewModel.session.shutdown()
@@ -133,14 +169,14 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
private fun showOverlay()
{
- overlay.isVisible = true
- overlay.animate()
+ binding.overlay.isVisible = true
+ binding.overlay.animate()
.alpha(1.0f)
.setListener(object: AnimatorListenerAdapter()
{
- override fun onAnimationEnd(animation: Animator?)
+ override fun onAnimationEnd(animation: Animator)
{
- overlay.alpha = 1.0f
+ binding.overlay.alpha = 1.0f
}
})
uiVisibilityHandler.removeCallbacks(hideSystemUIRunnable)
@@ -149,13 +185,13 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
private fun hideOverlay()
{
- overlay.animate()
+ binding.overlay.animate()
.alpha(0.0f)
.setListener(object: AnimatorListenerAdapter()
{
- override fun onAnimationEnd(animation: Animator?)
+ override fun onAnimationEnd(animation: Animator)
{
- overlay.isGone = true
+ binding.overlay.isGone = true
}
})
}
@@ -188,34 +224,39 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
private fun stateChanged(state: StreamState)
{
- progressBar.visibility = if(state == StreamStateConnecting) View.VISIBLE else View.GONE
+ binding.progressBar.visibility = if(state == StreamStateConnecting) View.VISIBLE else View.GONE
when(state)
{
is StreamStateQuit ->
{
- if(!state.reason.isStopped && dialogContents != StreamQuitDialog)
+ if(dialogContents != StreamQuitDialog)
{
- dialog?.dismiss()
- val reasonStr = state.reasonString
- val dialog = MaterialAlertDialogBuilder(this)
- .setMessage(getString(R.string.alert_message_session_quit, state.reason.toString())
- + (if(reasonStr != null) "\n$reasonStr" else ""))
- .setPositiveButton(R.string.action_reconnect) { _, _ ->
- dialog = null
- reconnect()
- }
- .setOnCancelListener {
- dialog = null
- finish()
- }
- .setNegativeButton(R.string.action_quit_session) { _, _ ->
- dialog = null
- finish()
- }
- .create()
- dialogContents = StreamQuitDialog
- dialog.show()
+ if(state.reason.isError)
+ {
+ dialog?.dismiss()
+ val reasonStr = state.reasonString
+ val dialog = MaterialAlertDialogBuilder(this)
+ .setMessage(getString(R.string.alert_message_session_quit, state.reason.toString())
+ + (if(reasonStr != null) "\n$reasonStr" else ""))
+ .setPositiveButton(R.string.action_reconnect) { _, _ ->
+ dialog = null
+ reconnect()
+ }
+ .setOnCancelListener {
+ dialog = null
+ finish()
+ }
+ .setNegativeButton(R.string.action_quit_session) { _, _ ->
+ dialog = null
+ finish()
+ }
+ .create()
+ dialogContents = StreamQuitDialog
+ dialog.show()
+ }
+ else
+ finish()
}
}
@@ -270,38 +311,90 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
dialog.show()
}
}
+
+ else -> {}
}
}
- private fun adjustTextureViewAspect()
+ private fun adjustTextureViewAspect(textureView: TextureView)
{
- val contentWidth = viewModel.session.connectInfo.videoProfile.width.toFloat()
- val contentHeight = viewModel.session.connectInfo.videoProfile.height.toFloat()
- val viewWidth = textureView.width.toFloat()
- val viewHeight = textureView.height.toFloat()
- val contentAspect = contentHeight / contentWidth
-
- val width: Float
- val height: Float
- if(viewHeight > viewWidth * contentAspect)
- {
- width = viewWidth
- height = viewWidth * contentAspect
- }
- else
- {
- width = viewHeight / contentAspect
- height = viewHeight
- }
-
+ val trans = TextureViewTransform(viewModel.session.connectInfo.videoProfile, textureView)
+ val resolution = trans.resolutionFor(TransformMode.fromButton(binding.displayModeToggle.checkedButtonId))
Matrix().also {
textureView.getTransform(it)
- it.setScale(width / viewWidth, height / viewHeight)
- it.postTranslate((viewWidth - width) * 0.5f, (viewHeight - height) * 0.5f)
+ it.setScale(resolution.width / trans.viewWidth, resolution.height / trans.viewHeight)
+ it.postTranslate((trans.viewWidth - resolution.width) * 0.5f, (trans.viewHeight - resolution.height) * 0.5f)
textureView.setTransform(it)
}
}
+ private fun adjustSurfaceViewAspect()
+ {
+ val videoProfile = viewModel.session.connectInfo.videoProfile
+ binding.aspectRatioLayout.aspectRatio = videoProfile.width.toFloat() / videoProfile.height.toFloat()
+ binding.aspectRatioLayout.mode = TransformMode.fromButton(binding.displayModeToggle.checkedButtonId)
+ }
+
+ private fun adjustStreamViewAspect() = adjustSurfaceViewAspect()
+
override fun dispatchKeyEvent(event: KeyEvent) = viewModel.input.dispatchKeyEvent(event) || super.dispatchKeyEvent(event)
override fun onGenericMotionEvent(event: MotionEvent) = viewModel.input.onGenericMotionEvent(event) || super.onGenericMotionEvent(event)
}
+
+enum class TransformMode
+{
+ FIT,
+ STRETCH,
+ ZOOM;
+
+ companion object
+ {
+ fun fromButton(displayModeButtonId: Int)
+ = when (displayModeButtonId)
+ {
+ R.id.display_mode_stretch_button -> STRETCH
+ R.id.display_mode_zoom_button -> ZOOM
+ else -> FIT
+ }
+ }
+}
+
+class TextureViewTransform(private val videoProfile: ConnectVideoProfile, private val textureView: TextureView)
+{
+ private val contentWidth : Float get() = videoProfile.width.toFloat()
+ private val contentHeight : Float get() = videoProfile.height.toFloat()
+ val viewWidth : Float get() = textureView.width.toFloat()
+ val viewHeight : Float get() = textureView.height.toFloat()
+ private val contentAspect : Float get() = contentHeight / contentWidth
+
+ fun resolutionFor(mode: TransformMode): Resolution
+ = when(mode)
+ {
+ TransformMode.STRETCH -> strechedResolution
+ TransformMode.ZOOM -> zoomedResolution
+ TransformMode.FIT -> normalResolution
+ }
+
+ private val strechedResolution get() = Resolution(viewWidth, viewHeight)
+
+ private val zoomedResolution get() =
+ if(viewHeight > viewWidth * contentAspect)
+ {
+ val zoomFactor = viewHeight / contentHeight
+ Resolution(contentWidth * zoomFactor, viewHeight)
+ }
+ else
+ {
+ val zoomFactor = viewWidth / contentWidth
+ Resolution(viewWidth, contentHeight * zoomFactor)
+ }
+
+ private val normalResolution get() =
+ if(viewHeight > viewWidth * contentAspect)
+ Resolution(viewWidth, viewWidth * contentAspect)
+ else
+ Resolution(viewHeight / contentAspect, viewHeight)
+}
+
+
+data class Resolution(val width: Float, val height: Float)
diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt
index 8f59f72..df69378 100644
--- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt
@@ -1,40 +1,31 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.stream
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
+import android.app.Application
+import android.content.Context
+import androidx.lifecycle.*
import com.metallic.chiaki.common.LogManager
import com.metallic.chiaki.session.StreamSession
import com.metallic.chiaki.common.Preferences
import com.metallic.chiaki.lib.*
import com.metallic.chiaki.session.StreamInput
-class StreamViewModel(val preferences: Preferences, val logManager: LogManager, val connectInfo: ConnectInfo): ViewModel()
+class StreamViewModel(val application: Application, val connectInfo: ConnectInfo): ViewModel()
{
+ val preferences = Preferences(application)
+ val logManager = LogManager(application)
+
private var _session: StreamSession? = null
- val input = StreamInput(preferences)
+ val input = StreamInput(application, preferences)
val session = StreamSession(connectInfo, logManager, preferences.logVerbose, input)
private var _onScreenControlsEnabled = MutableLiveData(preferences.onScreenControlsEnabled)
val onScreenControlsEnabled: LiveData get() = _onScreenControlsEnabled
+ private var _touchpadOnlyEnabled = MutableLiveData(preferences.touchpadOnlyEnabled)
+ val touchpadOnlyEnabled: LiveData get() = _touchpadOnlyEnabled
+
override fun onCleared()
{
super.onCleared()
@@ -46,4 +37,10 @@ class StreamViewModel(val preferences: Preferences, val logManager: LogManager,
preferences.onScreenControlsEnabled = enabled
_onScreenControlsEnabled.value = enabled
}
+
+ fun setTouchpadOnlyEnabled(enabled: Boolean)
+ {
+ preferences.touchpadOnlyEnabled = enabled
+ _touchpadOnlyEnabled.value = enabled
+ }
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt
index 5767532..0c0a310 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
@@ -78,7 +63,8 @@ class AnalogStickView @JvmOverloads constructor(
val center = center
if(center != null)
{
- drawableBase?.setBounds((center.x - radius).toInt(), (center.y - radius).toInt(), (center.x + radius).toInt(), (center.y + radius).toInt())
+ val circleRadius = radius + handleRadius
+ drawableBase?.setBounds((center.x - circleRadius).toInt(), (center.y - circleRadius).toInt(), (center.x + circleRadius).toInt(), (center.y + circleRadius).toInt())
drawableBase?.draw(canvas)
val handleX = center.x + handlePosition.x * radius
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonHaptics.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonHaptics.kt
new file mode 100644
index 0000000..909eab4
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonHaptics.kt
@@ -0,0 +1,29 @@
+package com.metallic.chiaki.touchcontrols
+
+import android.content.Context
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import androidx.appcompat.app.AppCompatActivity
+import com.metallic.chiaki.common.Preferences
+
+class ButtonHaptics(val context: Context)
+{
+ private val enabled = Preferences(context).buttonHapticEnabled
+
+ fun trigger(harder: Boolean = false)
+ {
+ if(!enabled)
+ return
+ val vibrator = context.getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ vibrator.vibrate(
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ VibrationEffect.createPredefined(if(harder) VibrationEffect.EFFECT_CLICK else VibrationEffect.EFFECT_TICK)
+ else
+ VibrationEffect.createOneShot(10, if(harder) 200 else 100)
+ )
+ else
+ vibrator.vibrate(10)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonView.kt
index 45116fd..66a9afe 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonView.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ButtonView.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
@@ -21,14 +6,19 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
+import android.util.Log
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.children
import com.metallic.chiaki.R
class ButtonView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
{
+ private val haptics = ButtonHaptics(context)
+
var buttonPressed = false
private set(value)
{
@@ -36,6 +26,8 @@ class ButtonView @JvmOverloads constructor(
field = value
if(diff)
{
+ if(value)
+ haptics.trigger()
invalidate()
buttonPressedCallback?.let { it(field) }
}
@@ -61,16 +53,39 @@ class ButtonView @JvmOverloads constructor(
{
super.onDraw(canvas)
val drawable = if(buttonPressed) drawablePressed else drawableIdle
- drawable?.setBounds(0, 0, width, height)
+ drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
drawable?.draw(canvas)
}
+ /**
+ * If this button overlaps with others in the same layout,
+ * let the one whose center is closest to the touch handle it.
+ */
+ private fun bestFittingTouchView(x: Float, y: Float): View
+ {
+ val loc = locationOnScreen + Vector(x, y)
+ return (parent as? ViewGroup)?.children?.filter {
+ it is ButtonView
+ }?.filter {
+ val pos = it.locationOnScreen
+ loc.x >= pos.x && loc.x < pos.x + it.width && loc.y >= pos.y && loc.y < pos.y + it.height
+ }?.sortedBy {
+ (loc - (it.locationOnScreen + Vector(it.width.toFloat(), it.height.toFloat()) * 0.5f)).lengthSq
+ }?.firstOrNull() ?: this
+ }
+
override fun onTouchEvent(event: MotionEvent): Boolean
{
- when(event.action)
+ when(event.actionMasked)
{
- MotionEvent.ACTION_DOWN -> buttonPressed = true
- MotionEvent.ACTION_UP -> buttonPressed = false
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
+ if(bestFittingTouchView(event.getX(event.actionIndex), event.getY(event.actionIndex)) != this)
+ return false
+ buttonPressed = true
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
+ buttonPressed = false
+ }
}
return true
}
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ControlsBackgroundView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ControlsBackgroundView.kt
index 2d5b8b1..3edbd86 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ControlsBackgroundView.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/ControlsBackgroundView.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt
index 06829ec..9c359d8 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
@@ -31,6 +16,8 @@ class DPadView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
{
+ private val haptics = ButtonHaptics(context)
+
enum class Direction {
LEFT,
RIGHT,
@@ -86,7 +73,7 @@ class DPadView @JvmOverloads constructor(
else
drawable = dpadIdleDrawable
- drawable?.setBounds(0, 0, width, height)
+ drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
//drawable?.alpha = 127
drawable?.draw(canvas)
}
@@ -128,6 +115,8 @@ class DPadView @JvmOverloads constructor(
if(state != newState)
{
+ if(newState != null)
+ haptics.trigger()
state = newState
invalidate()
stateChangeCallback?.let { it(state) }
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt
index 04e0394..b1ecfbe 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt
@@ -1,91 +1,99 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
-import com.metallic.chiaki.R
+import com.metallic.chiaki.databinding.FragmentControlsBinding
import com.metallic.chiaki.lib.ControllerState
-import kotlinx.android.synthetic.main.fragment_controls.*
+import io.reactivex.Observable
+import io.reactivex.rxkotlin.Observables.combineLatest
+import io.reactivex.subjects.BehaviorSubject
+import io.reactivex.subjects.Subject
-class TouchControlsFragment : Fragment()
+abstract class TouchControlsFragment : Fragment()
{
- private var controllerState = ControllerState()
- private set(value)
+ protected var ownControllerState = ControllerState()
+ set(value)
{
val diff = field != value
field = value
if(diff)
- controllerStateCallback?.let { it(value) }
+ ownControllerStateSubject.onNext(ownControllerState)
}
- var controllerStateCallback: ((ControllerState) -> Unit)? = null
- var onScreenControlsEnabled: LiveData? = null
+ protected val ownControllerStateSubject: Subject
+ = BehaviorSubject.create().also { it.onNext(ownControllerState) }
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
- = inflater.inflate(R.layout.fragment_controls, container, false)
+ // to delay attaching to the touchpadView until it's available
+ protected val controllerStateProxy: Subject>
+ = BehaviorSubject.create>().also { it.onNext(ownControllerStateSubject) }
+ val controllerState: Observable get() =
+ controllerStateProxy.flatMap { it }
+
+ var onScreenControlsEnabled: LiveData? = null
+}
+
+class DefaultTouchControlsFragment : TouchControlsFragment()
+{
+ private var _binding: FragmentControlsBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ FragmentControlsBinding.inflate(inflater, container, false).let {
+ _binding = it
+ controllerStateProxy.onNext(
+ combineLatest(ownControllerStateSubject, binding.touchpadView.controllerState) { a, b -> a or b }
+ )
+ it.root
+ }
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
super.onViewCreated(view, savedInstanceState)
- dpadView.stateChangeCallback = this::dpadStateChanged
- crossButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_CROSS)
- moonButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_MOON)
- pyramidButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PYRAMID)
- boxButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_BOX)
- l1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_L1)
- r1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_R1)
- optionsButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_OPTIONS)
- shareButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_SHARE)
- psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS)
- touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD)
+ binding.dpadView.stateChangeCallback = this::dpadStateChanged
+ binding.crossButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_CROSS)
+ binding.moonButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_MOON)
+ binding.pyramidButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PYRAMID)
+ binding.boxButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_BOX)
+ binding.l1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_L1)
+ binding.r1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_R1)
+ binding.l3ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_L3)
+ binding.r3ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_R3)
+ binding.optionsButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_OPTIONS)
+ binding.shareButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_SHARE)
+ binding.psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS)
- l2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { l2State = if(it) 255U else 0U } }
- r2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { r2State = if(it) 255U else 0U } }
+ binding.l2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { l2State = if(it) 255U else 0U } }
+ binding.r2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { r2State = if(it) 255U else 0U } }
val quantizeStick = { f: Float ->
- (Short.MAX_VALUE * f).toShort()
+ (Short.MAX_VALUE * f).toInt().toShort()
}
- leftAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply {
+ binding.leftAnalogStickView.stateChangedCallback = { ownControllerState = ownControllerState.copy().apply {
leftX = quantizeStick(it.x)
leftY = quantizeStick(it.y)
}}
- rightAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply {
+ binding.rightAnalogStickView.stateChangedCallback = { ownControllerState = ownControllerState.copy().apply {
rightX = quantizeStick(it.x)
rightY = quantizeStick(it.y)
}}
- onScreenControlsEnabled?.observe(this, Observer {
+ onScreenControlsEnabled?.observe(viewLifecycleOwner, Observer {
view.visibility = if(it) View.VISIBLE else View.GONE
})
}
private fun dpadStateChanged(direction: DPadView.Direction?)
{
- controllerState = controllerState.copy().apply {
+ ownControllerState = ownControllerState.copy().apply {
buttons = ((buttons
and ControllerState.BUTTON_DPAD_LEFT.inv()
and ControllerState.BUTTON_DPAD_RIGHT.inv()
@@ -107,7 +115,7 @@ class TouchControlsFragment : Fragment()
}
private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean ->
- controllerState = controllerState.copy().apply {
+ ownControllerState = ownControllerState.copy().apply {
buttons =
if(pressed)
buttons or buttonMask
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt
index 0d82e06..a1ead71 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
@@ -41,7 +26,7 @@ class TouchTracker
if(pointerId == null)
{
pointerId = event.getPointerId(event.actionIndex)
- currentPosition = Vector(event.x, event.y)
+ currentPosition = Vector(event.getX(event.actionIndex), event.getY(event.actionIndex))
}
}
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt
new file mode 100644
index 0000000..8910ef7
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+package com.metallic.chiaki.touchcontrols
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import com.metallic.chiaki.databinding.FragmentTouchpadOnlyBinding
+import io.reactivex.rxkotlin.Observables.combineLatest
+
+class TouchpadOnlyFragment : TouchControlsFragment()
+{
+ var touchpadOnlyEnabled: LiveData? = null
+
+ private var _binding: FragmentTouchpadOnlyBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ FragmentTouchpadOnlyBinding.inflate(inflater, container, false).let {
+ _binding = it
+ controllerStateProxy.onNext(
+ combineLatest(ownControllerStateSubject, binding.touchpadView.controllerState) { a, b -> a or b }
+ )
+ it.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?)
+ {
+ super.onViewCreated(view, savedInstanceState)
+ touchpadOnlyEnabled?.observe(viewLifecycleOwner, Observer {
+ view.visibility = if(it) View.VISIBLE else View.GONE
+ })
+ }
+
+ private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean ->
+ ownControllerState = ownControllerState.copy().apply {
+ buttons =
+ if(pressed)
+ buttons or buttonMask
+ else
+ buttons and buttonMask.inv()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt
new file mode 100644
index 0000000..a7e7b85
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+package com.metallic.chiaki.touchcontrols
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import com.metallic.chiaki.R
+import com.metallic.chiaki.lib.ControllerState
+import io.reactivex.Observable
+import io.reactivex.subjects.BehaviorSubject
+import io.reactivex.subjects.Subject
+import kotlin.math.max
+
+class TouchpadView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr)
+{
+ companion object
+ {
+ private const val BUTTON_PRESS_MAX_MOVE_DIST_DP = 32.0f
+ private const val SHORT_BUTTON_PRESS_DURATION_MS = 200L
+ private const val BUTTON_HOLD_DELAY_MS = 500L
+ }
+
+ private val haptics = ButtonHaptics(context)
+
+ private val drawableIdle: Drawable?
+ private val drawablePressed: Drawable?
+
+ private val state: ControllerState = ControllerState()
+
+ inner class Touch(
+ val stateId: UByte,
+ private val startX: Float,
+ private val startY: Float)
+ {
+ var lifted = false // will be true but touch still in list when only relevant for short touch
+ private var maxDist: Float = 0.0f
+ val moveInsignificant: Boolean get() = maxDist < BUTTON_PRESS_MAX_MOVE_DIST_DP
+
+ fun onMove(x: Float, y: Float)
+ {
+ val d = (Vector(x, y) - Vector(startX, startY)).length / resources.displayMetrics.density
+ maxDist = max(d, maxDist)
+ }
+
+ val startButtonHoldRunnable = Runnable {
+ if(!moveInsignificant || buttonHeld)
+ return@Runnable
+ haptics.trigger(true)
+ state.buttons = state.buttons or ControllerState.BUTTON_TOUCHPAD
+ buttonHeld = true
+ triggerStateChanged()
+ }
+ }
+ private val pointerTouches = mutableMapOf()
+
+ private val stateSubject: Subject
+ = BehaviorSubject.create().also { it.onNext(state) }
+ val controllerState: Observable get() = stateSubject
+
+ private var shortPressingTouches = listOf()
+ private val shortButtonPressLiftRunnable = Runnable {
+ state.buttons = state.buttons and ControllerState.BUTTON_TOUCHPAD.inv()
+ shortPressingTouches.forEach {
+ state.stopTouch(it.stateId)
+ }
+ shortPressingTouches = listOf()
+ triggerStateChanged()
+ }
+
+ private var buttonHeld = false
+
+ init
+ {
+ context.theme.obtainStyledAttributes(attrs, R.styleable.TouchpadView, 0, 0).apply {
+ drawableIdle = getDrawable(R.styleable.TouchpadView_drawableIdle)
+ drawablePressed = getDrawable(R.styleable.TouchpadView_drawablePressed)
+ recycle()
+ }
+ isClickable = true
+ }
+
+ override fun onDraw(canvas: Canvas)
+ {
+ super.onDraw(canvas)
+ if(pointerTouches.values.find { !it.lifted } == null)
+ return
+ val drawable = if(state.buttons and ControllerState.BUTTON_TOUCHPAD != 0U) drawablePressed else drawableIdle
+ drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
+ drawable?.draw(canvas)
+ }
+
+ private fun touchX(event: MotionEvent, index: Int): UShort =
+ maxOf(0U.toUShort(), minOf((ControllerState.TOUCHPAD_WIDTH - 1u).toUShort(),
+ (ControllerState.TOUCHPAD_WIDTH.toFloat() * event.getX(index) / width.toFloat()).toUInt().toUShort()))
+
+ private fun touchY(event: MotionEvent, index: Int): UShort =
+ maxOf(0U.toUShort(), minOf((ControllerState.TOUCHPAD_HEIGHT - 1u).toUShort(),
+ (ControllerState.TOUCHPAD_HEIGHT.toFloat() * event.getY(index) / height.toFloat()).toUInt().toUShort()))
+
+ override fun onTouchEvent(event: MotionEvent): Boolean
+ {
+ when(event.actionMasked)
+ {
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
+ state.startTouch(touchX(event, event.actionIndex), touchY(event, event.actionIndex))?.let {
+ haptics.trigger()
+ val touch = Touch(it, event.getX(event.actionIndex), event.getY(event.actionIndex))
+ pointerTouches[event.getPointerId(event.actionIndex)] = touch
+ if(!buttonHeld)
+ postDelayed(touch.startButtonHoldRunnable, BUTTON_HOLD_DELAY_MS)
+ triggerStateChanged()
+ }
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
+ pointerTouches.remove(event.getPointerId(event.actionIndex))?.let {
+ removeCallbacks(it.startButtonHoldRunnable)
+ when
+ {
+ buttonHeld ->
+ {
+ buttonHeld = false
+ state.buttons = state.buttons and ControllerState.BUTTON_TOUCHPAD.inv()
+ state.stopTouch(it.stateId)
+ }
+ it.moveInsignificant -> triggerShortButtonPress(it)
+ else -> state.stopTouch(it.stateId)
+ }
+ triggerStateChanged()
+ }
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val changed = pointerTouches.entries.fold(false) { acc, it ->
+ val index = event.findPointerIndex(it.key)
+ if(index < 0)
+ acc
+ else
+ {
+ it.value.onMove(event.getX(event.actionIndex), event.getY(event.actionIndex))
+ acc || state.setTouchPos(it.value.stateId, touchX(event, index), touchY(event, index))
+ }
+ }
+ if(changed)
+ triggerStateChanged()
+ }
+ }
+ return true
+ }
+
+ private fun triggerShortButtonPress(touch: Touch)
+ {
+ shortPressingTouches = shortPressingTouches + listOf(touch)
+ removeCallbacks(shortButtonPressLiftRunnable)
+ state.buttons = state.buttons or ControllerState.BUTTON_TOUCHPAD
+ postDelayed(shortButtonPressLiftRunnable, SHORT_BUTTON_PRESS_DURATION_MS)
+ }
+
+ private fun triggerStateChanged()
+ {
+ invalidate()
+ stateSubject.onNext(state)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt
index be1fe4d..208c41a 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt
@@ -1,22 +1,8 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
+import android.view.View
import kotlin.math.sqrt
data class Vector(val x: Float, val y: Float)
@@ -33,4 +19,10 @@ data class Vector(val x: Float, val y: Float)
val lengthSq get() = x*x + y*y
val length get() = sqrt(lengthSq)
val normalized get() = this / length
+}
+
+val View.locationOnScreen: Vector get() {
+ val v = intArrayOf(0, 0)
+ this.getLocationOnScreen(v)
+ return Vector(v[0].toFloat(), v[1].toFloat())
}
\ No newline at end of file
diff --git a/android/app/src/main/res/color/stream_material_button_icon_tint.xml b/android/app/src/main/res/color/stream_material_button_icon_tint.xml
new file mode 100644
index 0000000..fbfbf5c
--- /dev/null
+++ b/android/app/src/main/res/color/stream_material_button_icon_tint.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/control_button_l1.xml b/android/app/src/main/res/drawable/control_button_l1.xml
index a709f25..17f9594 100644
--- a/android/app/src/main/res/drawable/control_button_l1.xml
+++ b/android/app/src/main/res/drawable/control_button_l1.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_l1_pressed.xml b/android/app/src/main/res/drawable/control_button_l1_pressed.xml
index b8bcce0..0267190 100644
--- a/android/app/src/main/res/drawable/control_button_l1_pressed.xml
+++ b/android/app/src/main/res/drawable/control_button_l1_pressed.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_l2.xml b/android/app/src/main/res/drawable/control_button_l2.xml
index 6867aa0..8e75f40 100644
--- a/android/app/src/main/res/drawable/control_button_l2.xml
+++ b/android/app/src/main/res/drawable/control_button_l2.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_l2_pressed.xml b/android/app/src/main/res/drawable/control_button_l2_pressed.xml
index e4df2de..b120891 100644
--- a/android/app/src/main/res/drawable/control_button_l2_pressed.xml
+++ b/android/app/src/main/res/drawable/control_button_l2_pressed.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_l3.xml b/android/app/src/main/res/drawable/control_button_l3.xml
new file mode 100644
index 0000000..34c3382
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_button_l3.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/control_button_l3_pressed.xml b/android/app/src/main/res/drawable/control_button_l3_pressed.xml
new file mode 100644
index 0000000..43f239a
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_button_l3_pressed.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/control_button_r1.xml b/android/app/src/main/res/drawable/control_button_r1.xml
index a0b3262..d614419 100644
--- a/android/app/src/main/res/drawable/control_button_r1.xml
+++ b/android/app/src/main/res/drawable/control_button_r1.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_r1_pressed.xml b/android/app/src/main/res/drawable/control_button_r1_pressed.xml
index 5cadcb6..8d51983 100644
--- a/android/app/src/main/res/drawable/control_button_r1_pressed.xml
+++ b/android/app/src/main/res/drawable/control_button_r1_pressed.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_r2.xml b/android/app/src/main/res/drawable/control_button_r2.xml
index c6a625d..dd16994 100644
--- a/android/app/src/main/res/drawable/control_button_r2.xml
+++ b/android/app/src/main/res/drawable/control_button_r2.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_r2_pressed.xml b/android/app/src/main/res/drawable/control_button_r2_pressed.xml
index 260e60b..7cc84b1 100644
--- a/android/app/src/main/res/drawable/control_button_r2_pressed.xml
+++ b/android/app/src/main/res/drawable/control_button_r2_pressed.xml
@@ -1,13 +1,12 @@
+ android:viewportHeight="67.73334">
diff --git a/android/app/src/main/res/drawable/control_button_r3.xml b/android/app/src/main/res/drawable/control_button_r3.xml
new file mode 100644
index 0000000..0f2bcfd
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_button_r3.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/control_button_r3_pressed.xml b/android/app/src/main/res/drawable/control_button_r3_pressed.xml
new file mode 100644
index 0000000..08635e8
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_button_r3_pressed.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/control_button_touchpad.xml b/android/app/src/main/res/drawable/control_button_touchpad.xml
deleted file mode 100644
index 40c0465..0000000
--- a/android/app/src/main/res/drawable/control_button_touchpad.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/android/app/src/main/res/drawable/control_button_touchpad_pressed.xml b/android/app/src/main/res/drawable/control_button_touchpad_pressed.xml
deleted file mode 100644
index 8255860..0000000
--- a/android/app/src/main/res/drawable/control_button_touchpad_pressed.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/android/app/src/main/res/drawable/control_touchpad.xml b/android/app/src/main/res/drawable/control_touchpad.xml
new file mode 100644
index 0000000..509b8a3
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_touchpad.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/control_touchpad_pressed.xml b/android/app/src/main/res/drawable/control_touchpad_pressed.xml
new file mode 100644
index 0000000..b4f3f16
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_touchpad_pressed.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_button_haptic.xml b/android/app/src/main/res/drawable/ic_button_haptic.xml
new file mode 100644
index 0000000..fea4c99
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_button_haptic.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_codec.xml b/android/app/src/main/res/drawable/ic_codec.xml
new file mode 100644
index 0000000..e173d90
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_codec.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_console_ps5.xml b/android/app/src/main/res/drawable/ic_console_ps5.xml
new file mode 100644
index 0000000..488213b
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_console_ps5.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_console_ps5_ready.xml b/android/app/src/main/res/drawable/ic_console_ps5_ready.xml
new file mode 100644
index 0000000..d2a3ab0
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_console_ps5_ready.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_console_ps5_standby.xml b/android/app/src/main/res/drawable/ic_console_ps5_standby.xml
new file mode 100644
index 0000000..8d49bbd
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_console_ps5_standby.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_display_normal.xml b/android/app/src/main/res/drawable/ic_display_normal.xml
new file mode 100644
index 0000000..4ffbf8f
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_display_normal.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/ic_display_stretch.xml b/android/app/src/main/res/drawable/ic_display_stretch.xml
new file mode 100644
index 0000000..605cf01
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_display_stretch.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/ic_display_zoom.xml b/android/app/src/main/res/drawable/ic_display_zoom.xml
new file mode 100644
index 0000000..c260698
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_display_zoom.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/ic_export.xml b/android/app/src/main/res/drawable/ic_export.xml
new file mode 100644
index 0000000..f92f317
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_export.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_import.xml b/android/app/src/main/res/drawable/ic_import.xml
new file mode 100644
index 0000000..fceae15
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_import.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_motion.xml b/android/app/src/main/res/drawable/ic_motion.xml
new file mode 100644
index 0000000..c5a4ea1
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_motion.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_rumble.xml b/android/app/src/main/res/drawable/ic_rumble.xml
new file mode 100644
index 0000000..4ecdb61
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_rumble.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/layout/activity_regist.xml b/android/app/src/main/res/layout/activity_regist.xml
index fff5cad..7a8398b 100644
--- a/android/app/src/main/res/layout/activity_regist.xml
+++ b/android/app/src/main/res/layout/activity_regist.xml
@@ -66,22 +66,39 @@
android:id="@+id/ps4VersionRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
+ android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/broadcastCheckBox">
+
+
+
+
+ android:checked="false"
+ android:text="@string/regist_option_ps4_ge_7" />
+
+ android:layout_weight="1"/>
diff --git a/android/app/src/main/res/layout/activity_stream.xml b/android/app/src/main/res/layout/activity_stream.xml
index 27a32d0..6a7f9a3 100644
--- a/android/app/src/main/res/layout/activity_stream.xml
+++ b/android/app/src/main/res/layout/activity_stream.xml
@@ -1,15 +1,24 @@
-
+ tools:context=".stream.StreamActivity"
+ android:keepScreenOn="true">
-
+ android:layout_height="match_parent"
+ android:layout_gravity="center">
+
+
+
+
@@ -27,9 +42,6 @@
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="bottom"
- tools:paddingRight="48dp"
- tools:paddingTop="25dp"
android:fitsSystemWindows="true">
-
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/fragment_controls.xml b/android/app/src/main/res/layout/fragment_controls.xml
index a2c24df..7a6b3af 100644
--- a/android/app/src/main/res/layout/fragment_controls.xml
+++ b/android/app/src/main/res/layout/fragment_controls.xml
@@ -1,10 +1,12 @@
+ android:clipChildren="false"
+ tools:ignore="RtlHardcoded,RtlSymmetry">
+
+
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+
+
+
+
-
-
+ app:layout_constraintTop_toTopOf="parent"/>
+ app:layout_constraintTop_toTopOf="parent"/>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/item_display_host.xml b/android/app/src/main/res/layout/item_display_host.xml
index 21aedc5..116e0bd 100644
--- a/android/app/src/main/res/layout/item_display_host.xml
+++ b/android/app/src/main/res/layout/item_display_host.xml
@@ -88,11 +88,13 @@
android:id="@+id/stateIndicatorImageView"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:src="@drawable/ic_console"
+ android:src="@drawable/ic_console_ps5"
app:layout_constraintTop_toBottomOf="@id/idTextView"
+ app:layout_constraintBottom_toTopOf="@id/bottomInfoTextView"
android:layout_marginLeft="32dp"
android:layout_marginRight="32dp"
- android:layout_marginTop="8dp"/>
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"/>
+
+
+
-
-
+
+
@@ -11,4 +14,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
index e71e7d2..9ee2e9b 100644
--- a/android/app/src/main/res/values/dimens.xml
+++ b/android/app/src/main/res/values/dimens.xml
@@ -1,7 +1,11 @@
- 48dp
- 64dp
- 16dp
+ 88dp
+ 176dp
+ 24dp
+ 16dp
+ 64dp
+ 48dp
+ 32dp48dp
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index fb89b5c..254042b 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -21,16 +21,20 @@
App: %s\nTitle ID: %sRegister ConsoleAdd Console Manually
- On the PS4, navigate to
- Settings →\nRemote Play Connection Settings →\nAdd Device
+ On the PS4, navigate to
+ Settings →\nRemote Play Connection Settings →\nAdd Device
+ On the PS5, navigate to
+ Settings →\nSystem → Remote Play →\nLink Deviceto obtain the PINRegister ConsoleHostBroadcastPS4 < 7.0
- PS4 ≥ 7.0
+ PS4 ≥ 7.0, < 8
+ PS4 ≥ 8.0
+ PS5About obtaining your Account ID, see
- https://github.com/thestr4ng3r/chiaki/blob/master/README.md#obtaining-your-psn-accountid
+ https://git.sr.ht/~thestr4ng3r/chiaki/tree/master/item/README.md#obtaining-your-psn-accountidPSN Online ID (username, case-sensitive)PSN Account ID (8 bytes, base64)PIN
@@ -40,24 +44,37 @@
Please enter a valid 8-byte Account ID in Base64Please enter a valid %d-digit PINShare Log
+ Export SettingsRegist successful.Regist failed.The console with MAC %s has already been registered. Should the previous record be overwritten?OverwriteCancel
+ Import
+ Cancel
+ Import
+ Registered Hosts: %s\n\nManual Hosts: %s
+ Failed to import settings from file:\n%s
+ DismissManual Console EntrySaveRegistered ConsoleRegister on first ConnectionGeneralStream
+ ExportRegistered ConsolesSession LogsCollected log files from previous sessions for debugging
+ Export Settings
+ Warning: These resulting file can contain your secret Remote Play keys! Do not share them.
+ Import Settings
+ Import Settings from JSONCurrently registered: %dResolutionFPSBitrate
+ CodecVerbose LoggingWarning: This logs a LOT! Don\'t enable for regular use.360p
@@ -67,8 +84,16 @@
3060Auto (%d)
+ H264
+ H265 (PS5 only)Swap Cross/Moon and Box/Pyramid ButtonsSwap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers)
+ Rumble
+ Use phone vibration motor for rumble
+ Motion
+ Use device\'s motion sensors for controller motion
+ Touch Haptics
+ Use phone vibration motor for short haptic feedback on button touchesAre you sure you want to delete the registered console %s with ID %s?Are you sure you want to delete the console entry for %s?Keep
@@ -82,9 +107,16 @@
discovery_enabledon_screen_controls_enabled
+ touchpad_only_enabled
+ rumble_enabled
+ motion_enabled
+ button_haptic_enabledlog_verbose
+ import_settings
+ export_settingsswap_cross_moonstream_resolutionstream_fpsstream_bitrate
+ stream_codec
diff --git a/android/app/src/main/res/xml/filepaths.xml b/android/app/src/main/res/xml/filepaths.xml
index f3315d2..038db8c 100644
--- a/android/app/src/main/res/xml/filepaths.xml
+++ b/android/app/src/main/res/xml/filepaths.xml
@@ -3,4 +3,7 @@
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/preferences.xml b/android/app/src/main/res/xml/preferences.xml
index 02d091f..49989db 100644
--- a/android/app/src/main/res/xml/preferences.xml
+++ b/android/app/src/main/res/xml/preferences.xml
@@ -19,6 +19,24 @@
app:summary="@string/preferences_swap_cross_moon_summary"
app:icon="@drawable/ic_gamepad" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index e3245e7..cdfc5d6 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.8.0'
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.1'
+ classpath 'com.android.tools.build:gradle:7.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 2362665..7a0d628 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Sep 14 10:53:58 CEST 2019
+#Sun Feb 05 16:25:19 CET 2023
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/assets/chiaki_macos.svg b/assets/chiaki_macos.svg
new file mode 100644
index 0000000..a4ce8f4
--- /dev/null
+++ b/assets/chiaki_macos.svg
@@ -0,0 +1,266 @@
+
+
+
+
diff --git a/assets/chiaki_macos_simple.svg b/assets/chiaki_macos_simple.svg
new file mode 100644
index 0000000..7f1359d
--- /dev/null
+++ b/assets/chiaki_macos_simple.svg
@@ -0,0 +1,102 @@
+
+
+
+
diff --git a/assets/chiaki_wide_simple.svg b/assets/chiaki_wide_simple.svg
new file mode 100644
index 0000000..4ce93ac
--- /dev/null
+++ b/assets/chiaki_wide_simple.svg
@@ -0,0 +1,147 @@
+
+
diff --git a/assets/console.svg b/assets/console.svg
index 00fba66..99eadb9 100644
--- a/assets/console.svg
+++ b/assets/console.svg
@@ -1,6 +1,4 @@
-
-
diff --git a/assets/console2.svg b/assets/console2.svg
new file mode 100644
index 0000000..720fa37
--- /dev/null
+++ b/assets/console2.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/assets/controls/l1.svg b/assets/controls/l1.svg
index ce67e89..9d3c3f9 100644
--- a/assets/controls/l1.svg
+++ b/assets/controls/l1.svg
@@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- sodipodi:docname="l1.svg"
- inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8"
version="1.1"
- viewBox="0 0 67.733332 33.866668"
- height="128"
+ viewBox="0 0 67.733332 67.733336"
+ height="256"
width="256">
-
@@ -49,28 +25,12 @@
-
-
+ id="g2115"
+ style="display:inline">
+ id="path2031"
+ style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
+ d="M 148.25391,0 0,148.25391 77.041016,225.29492 a 104.83116,104.83117 0 0 0 148.253904,0 104.83116,104.83117 0 0 0 0,-148.253904 z M 96.048828,111.22656 h 10.482422 v 68.65235 h 37.72656 v 8.82226 H 96.048828 Z m 78.980472,0 h 10.48242 v 68.65235 h 17.12305 v 8.82226 h -44.62696 v -8.82226 h 17.125 v -59.10547 l -18.6289,3.73633 v -9.54688 z"
+ transform="scale(0.26458334)" />
diff --git a/assets/controls/l2.svg b/assets/controls/l2.svg
index 01e886f..4d561e0 100644
--- a/assets/controls/l2.svg
+++ b/assets/controls/l2.svg
@@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- sodipodi:docname="l2.svg"
- inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8"
version="1.1"
- viewBox="0 0 67.733332 33.866668"
- height="128"
+ viewBox="0 0 67.733332 67.733336"
+ height="256"
width="256">
-
@@ -49,28 +25,12 @@
-
-
+ id="g2135"
+ style="display:inline">
+ id="path2127"
+ style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
+ d="m 104.51367,0 a 104.83116,104.83117 0 0 0 -73.808592,30.705078 104.83116,104.83117 0 0 0 0,148.253902 L 107.74609,256 256,107.74609 178.95898,30.705078 A 104.83116,104.83117 0 0 0 104.51367,0 Z m 27.39844,57.390625 c 8.02592,0 14.42713,2.00657 19.20117,6.019531 4.77404,4.012961 7.16016,9.374608 7.16016,16.085938 0,3.182692 -0.6056,6.210686 -1.81641,9.082031 -1.17621,2.836747 -3.33823,6.191824 -6.48633,10.066406 -0.86486,1.003241 -3.61434,3.908169 -8.25,8.716799 -4.63566,4.77404 -11.17418,11.468 -19.61523,20.08203 h 36.58398 v 8.82226 h -49.19336 v -8.82226 c 3.97837,-4.11675 9.39248,-9.63383 16.24219,-16.55274 6.8843,-6.95349 11.20834,-11.432968 12.97266,-13.439448 3.35566,-3.770799 5.69127,-6.954243 7.00586,-9.548828 1.34918,-2.629183 2.02343,-5.207025 2.02343,-7.732422 0,-4.116745 -1.45343,-7.471821 -4.35937,-10.066406 -2.87134,-2.594588 -6.62411,-3.892578 -11.25977,-3.892578 -3.28647,-10e-7 -6.76462,0.571271 -10.43164,1.71289 -3.63242,1.141619 -7.52444,2.871623 -11.67578,5.189453 V 62.527344 c 4.22053,-1.695131 8.16502,-2.974982 11.83203,-3.839844 3.66702,-0.864862 7.0221,-1.296875 10.06641,-1.296875 z m -78.978516,1.400391 h 10.482422 v 68.652344 h 37.724604 v 8.82226 H 52.933594 Z"
+ transform="scale(0.26458334)" />
diff --git a/assets/controls/l3.svg b/assets/controls/l3.svg
new file mode 100644
index 0000000..464e440
--- /dev/null
+++ b/assets/controls/l3.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/assets/controls/l3_raw.svg b/assets/controls/l3_raw.svg
new file mode 100644
index 0000000..f04135f
--- /dev/null
+++ b/assets/controls/l3_raw.svg
@@ -0,0 +1,44 @@
+
+
diff --git a/assets/controls/lr12.svg b/assets/controls/lr12.svg
new file mode 100644
index 0000000..874475f
--- /dev/null
+++ b/assets/controls/lr12.svg
@@ -0,0 +1,124 @@
+
+
diff --git a/assets/controls/r1.svg b/assets/controls/r1.svg
index ef51bc2..70f881c 100644
--- a/assets/controls/r1.svg
+++ b/assets/controls/r1.svg
@@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- sodipodi:docname="r1.svg"
- inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8"
version="1.1"
- viewBox="0 0 67.733332 33.866668"
- height="128"
+ viewBox="0 0 67.733332 67.733336"
+ height="256"
width="256">
-
@@ -49,28 +25,12 @@
-
-
+ id="g2125"
+ style="display:inline">
+ id="path2117"
+ style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
+ d="M 107.74609,0 30.705078,77.041016 a 104.83116,104.83117 0 0 0 0,148.253904 104.83116,104.83117 0 0 0 148.253902,0 L 256,148.25391 Z M 46.933594,111.22656 h 23.662109 c 8.856189,0 15.463362,1.85113 19.822266,5.55274 4.358906,3.70161 6.539062,9.28735 6.539062,16.75976 0,4.87783 -1.140593,8.92529 -3.423828,12.14258 -2.248642,3.21729 -5.535064,5.44991 -9.859375,6.69531 2.248643,0.76108 4.426845,2.38615 6.53711,4.87696 2.144856,2.4908 4.290687,5.91648 6.435546,10.27539 L 107.2832,188.70117 H 96.023438 l -9.91211,-19.875 C 83.551336,163.637 81.060286,160.19513 78.638672,158.5 c -2.387019,-1.69513 -5.655301,-2.54297 -9.806641,-2.54297 H 57.416016 v 32.74414 H 46.933594 Z m 93.613286,0 h 10.48242 v 68.65235 h 17.12304 v 8.82226 h -44.62695 v -8.82226 h 17.125 v -59.10547 l -18.62891,3.73633 v -9.54688 z m -83.130864,8.61328 v 27.50196 h 13.179687 c 5.050794,0 8.856023,-1.15874 11.416016,-3.47657 2.594587,-2.35242 3.892578,-5.79429 3.892578,-10.32617 0,-4.53188 -1.297991,-7.93942 -3.892578,-10.22265 -2.559993,-2.31783 -6.365222,-3.47657 -11.416016,-3.47657 z"
+ transform="scale(0.26458334)" />
diff --git a/assets/controls/r2.svg b/assets/controls/r2.svg
index 350ffaa..c32639d 100644
--- a/assets/controls/r2.svg
+++ b/assets/controls/r2.svg
@@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- sodipodi:docname="r2.svg"
- inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8"
version="1.1"
- viewBox="0 0 67.733332 33.866668"
- height="128"
+ viewBox="0 0 67.733332 67.733336"
+ height="256"
width="256">
-
@@ -49,28 +25,13 @@
-
-
+ transform="matrix(-1,0,0,1,67.733334,0)">
+ id="path2137"
+ style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
+ d="M 77.041016,30.705078 0,107.74609 148.25391,256 225.29492,178.95898 a 104.83116,104.83117 0 0 0 0,-148.253902 104.83116,104.83117 0 0 0 -148.253904,0 z M 184.92383,57.390625 c 8.02592,0 14.42518,2.00657 19.19922,6.019531 4.77403,4.012961 7.16211,9.374608 7.16211,16.085938 0,3.182692 -0.6056,6.210686 -1.81641,9.082031 -1.17621,2.836747 -3.33823,6.191824 -6.48633,10.066406 -0.86486,1.003241 -3.61629,3.908169 -8.25195,8.716799 -4.63566,4.77404 -11.17223,11.468 -19.61328,20.08203 h 36.58203 v 8.82226 h -49.19336 v -8.82226 c 3.97837,-4.11675 9.39248,-9.63383 16.24219,-16.55274 6.8843,-6.95349 11.20833,-11.432968 12.97265,-13.439448 3.35567,-3.770799 5.69127,-6.954243 7.00586,-9.548828 1.34919,-2.629183 2.02344,-5.207025 2.02344,-7.732422 0,-4.116745 -1.45148,-7.471821 -4.35742,-10.066406 -2.87134,-2.594588 -6.62606,-3.892578 -11.26172,-3.892578 -3.28648,-10e-7 -6.76267,0.571271 -10.42969,1.71289 -3.63242,1.141619 -7.52444,2.871623 -11.67578,5.189453 V 62.527344 c 4.22053,-1.695131 8.16502,-2.974982 11.83203,-3.839844 3.66702,-0.864862 7.02209,-1.296875 10.06641,-1.296875 z m -93.613283,1.400391 h 23.664063 c 8.85619,0 15.46335,1.851125 19.82227,5.552734 4.3589,3.701611 6.5371,9.289311 6.5371,16.761719 0,4.877823 -1.14059,8.92529 -3.42382,12.142578 -2.24865,3.217287 -5.53507,5.447957 -9.85938,6.693359 2.24864,0.761074 4.42685,2.386144 6.53711,4.876954 2.14486,2.4908 4.29069,5.91648 6.43555,10.27539 l 10.63867,21.17187 h -11.26172 l -9.91016,-19.875 c -2.55999,-5.18917 -5.05104,-8.63104 -7.47265,-10.32617 -2.38702,-1.69513 -5.65726,-2.54297 -9.8086,-2.54297 h -11.41601 v 32.74414 H 91.310547 Z m 10.482423,8.615234 v 27.501953 h 13.18164 c 5.05079,0 8.85602,-1.158732 11.41601,-3.476562 2.5946,-2.352424 3.89063,-5.794292 3.89063,-10.326172 0,-4.531878 -1.29603,-7.93942 -3.89063,-10.222657 -2.55999,-2.31783 -6.36522,-3.476562 -11.41601,-3.476562 z"
+ transform="matrix(-0.26458334,0,0,0.26458334,67.733334,0)" />
diff --git a/assets/controls/r3.svg b/assets/controls/r3.svg
new file mode 100644
index 0000000..b5f1fa7
--- /dev/null
+++ b/assets/controls/r3.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/assets/controls/r3_raw.svg b/assets/controls/r3_raw.svg
new file mode 100644
index 0000000..5f47c8e
--- /dev/null
+++ b/assets/controls/r3_raw.svg
@@ -0,0 +1,44 @@
+
+
diff --git a/assets/controls/touchpad_surface.svg b/assets/controls/touchpad_surface.svg
new file mode 100644
index 0000000..7476128
--- /dev/null
+++ b/assets/controls/touchpad_surface.svg
@@ -0,0 +1,65 @@
+
+
diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt
index c718185..83de1f9 100644
--- a/cli/CMakeLists.txt
+++ b/cli/CMakeLists.txt
@@ -1,7 +1,8 @@
set(SOURCE
include/chiaki-cli.h
- src/discover.c)
+ src/discover.c
+ src/wakeup.c)
add_library(chiaki-cli-lib STATIC ${SOURCE})
target_include_directories(chiaki-cli-lib PUBLIC "include")
@@ -14,3 +15,4 @@ endif()
add_executable(chiaki-cli src/main.c)
target_link_libraries(chiaki-cli chiaki-cli-lib)
+install(TARGETS chiaki-cli)
diff --git a/cli/include/chiaki-cli.h b/cli/include/chiaki-cli.h
index 0bd1e1b..91ee0dd 100644
--- a/cli/include/chiaki-cli.h
+++ b/cli/include/chiaki-cli.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_CHIAKI_CLI_H
#define CHIAKI_CHIAKI_CLI_H
@@ -26,6 +11,7 @@ extern "C" {
#endif
CHIAKI_EXPORT int chiaki_cli_cmd_discover(ChiakiLog *log, int argc, char *argv[]);
+CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[]);
#ifdef __cplusplus
}
diff --git a/cli/src/discover.c b/cli/src/discover.c
index 04f3f74..cf9319f 100644
--- a/cli/src/discover.c
+++ b/cli/src/discover.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -157,16 +142,22 @@ CHIAKI_EXPORT int chiaki_cli_cmd_discover(ChiakiLog *log, int argc, char *argv[]
return 1;
}
- ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT); // TODO: IPv6
-
ChiakiDiscoveryPacket packet;
memset(&packet, 0, sizeof(packet));
packet.cmd = CHIAKI_DISCOVERY_CMD_SRCH;
-
- chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len);
+ packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS4;
+ ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS4);
+ err = chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len);
+ if(err != CHIAKI_ERR_SUCCESS)
+ CHIAKI_LOGE(log, "Failed to send discovery packet for PS4: %s", chiaki_error_string(err));
+ packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5;
+ ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS5);
+ err = chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len);
+ if(err != CHIAKI_ERR_SUCCESS)
+ CHIAKI_LOGE(log, "Failed to send discovery packet for PS5: %s", chiaki_error_string(err));
while(1)
- sleep(1);
+ sleep(1); // TODO: wtf
return 0;
}
diff --git a/cli/src/main.c b/cli/src/main.c
index 7395ba8..3f7747a 100644
--- a/cli/src/main.c
+++ b/cli/src/main.c
@@ -1,19 +1,4 @@
-/*
-* This file is part of Chiaki.
-*
-* Chiaki is free software: you can redistribute it and/or modify
-* it under the terms of the GNU General Public License as published by
-* the Free Software Foundation, either version 3 of the License, or
-* (at your option) any later version.
-*
-* Chiaki is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with Chiaki. If not, see .
-*/
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -24,10 +9,11 @@
#include
static const char doc[] =
- "CLI for Chiaki (PS4 Remote Play Client)"
+ "CLI for Chiaki (PlayStation Remote Play Client)"
"\v"
"Supported commands are:\n"
- " discover Discover Consoles.\n";
+ " discover Discover Consoles.\n"
+ " wakeup Send Wakeup Packet.\n";
#define ARG_KEY_VERBOSE 'v'
@@ -73,6 +59,8 @@ static int parse_opt(int key, char *arg, struct argp_state *state)
case ARGP_KEY_ARG:
if(strcmp(arg, "discover") == 0)
exit(call_subcmd(state, "discover", chiaki_cli_cmd_discover));
+ else if(strcmp(arg, "wakeup") == 0)
+ exit(call_subcmd(state, "wakeup", chiaki_cli_cmd_wakeup));
// fallthrough
case ARGP_KEY_END:
argp_usage(state);
diff --git a/cli/src/wakeup.c b/cli/src/wakeup.c
new file mode 100644
index 0000000..cea2792
--- /dev/null
+++ b/cli/src/wakeup.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include
+
+#include
+
+#include
+#include
+
+static char doc[] = "Send a PS4 wakeup packet.";
+
+#define ARG_KEY_HOST 'h'
+#define ARG_KEY_REGISTKEY 'r'
+#define ARG_KEY_PS4 '4'
+#define ARG_KEY_PS5 '5'
+
+static struct argp_option options[] = {
+ { "host", ARG_KEY_HOST, "Host", 0, "Host to send wakeup packet to", 0 },
+ { "registkey", ARG_KEY_REGISTKEY, "RegistKey", 0, "Remote Play registration key (plaintext)", 0 },
+ { "ps4", ARG_KEY_PS4, NULL, 0, "PlayStation 4", 0 },
+ { "ps5", ARG_KEY_PS5, NULL, 0, "PlayStation 5 (default)", 0 },
+ { 0 }
+};
+
+typedef struct arguments
+{
+ const char *host;
+ const char *registkey;
+ bool ps5;
+} Arguments;
+
+static int parse_opt(int key, char *arg, struct argp_state *state)
+{
+ Arguments *arguments = state->input;
+
+ switch(key)
+ {
+ case ARG_KEY_HOST:
+ arguments->host = arg;
+ break;
+ case ARG_KEY_REGISTKEY:
+ arguments->registkey = arg;
+ break;
+ case ARGP_KEY_ARG:
+ argp_usage(state);
+ break;
+ case ARG_KEY_PS4:
+ arguments->ps5 = false;
+ break;
+ case ARG_KEY_PS5:
+ arguments->ps5 = true;
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static struct argp argp = { options, parse_opt, 0, doc, 0, 0, 0 };
+
+CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[])
+{
+ Arguments arguments = { 0 };
+ arguments.ps5 = true;
+ error_t argp_r = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &arguments);
+ if(argp_r != 0)
+ return 1;
+
+ if(!arguments.host)
+ {
+ fprintf(stderr, "No host specified, see --help.\n");
+ return 1;
+ }
+ if(!arguments.registkey)
+ {
+ fprintf(stderr, "No registration key specified, see --help.\n");
+ return 1;
+ }
+ if(strlen(arguments.registkey) > 8)
+ {
+ fprintf(stderr, "Given registkey is too long.\n");
+ return 1;
+ }
+
+ uint64_t credential = (uint64_t)strtoull(arguments.registkey, NULL, 16);
+
+ return chiaki_discovery_wakeup(log, NULL, arguments.host, credential, arguments.ps5);
+}
diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake
index 8ad5ec9..bdb66aa 100644
--- a/cmake/FindFFMPEG.cmake
+++ b/cmake/FindFFMPEG.cmake
@@ -42,25 +42,45 @@ function(join OUTPUT GLUE)
endfunction()
function (_ffmpeg_find component headername)
+ if(TARGET "FFMPEG::${component}")
+ # already found before
+ return()
+ endif()
+
# Try pkg-config first
if(PKG_CONFIG_FOUND)
- pkg_check_modules(FFMPEG_${component} lib${component} IMPORTED_TARGET)
+ if(CMAKE_VERSION VERSION_LESS "3.6")
+ pkg_check_modules(FFMPEG_${component} lib${component})
+ else()
+ pkg_check_modules(FFMPEG_${component} lib${component} IMPORTED_TARGET)
+ endif()
if(FFMPEG_${component}_FOUND)
- if(APPLE)
- join(FFMPEG_LDFLAGS_STRING " " ${FFMPEG_${component}_LDFLAGS})
- string(REGEX REPLACE "-Wl,-framework,([^ ]+)" "-framework \\1" FFMPEG_LDFLAGS_STRING_CLEAN ${FFMPEG_LDFLAGS_STRING})
- string(REGEX MATCHALL "-framework [^ ]+" FFMPEG_FRAMEWORKS ${FFMPEG_LDFLAGS_STRING_CLEAN})
- list(APPEND FFMPEG_${component}_LIBRARIES ${FFMPEG_FRAMEWORKS})
- set_target_properties(PkgConfig::FFMPEG_${component} PROPERTIES
- INTERFACE_LINK_DIRECTORIES "${FFMPEG_${component}_LIBRARY_DIRS}"
- INTERFACE_LINK_LIBRARIES "${FFMPEG_${component}_LIBRARIES}"
- INTERFACE_LINK_OPTIONS "")
- message("set libs to \"${FFMPEG_${component}_LIBRARIES}\"")
- message("set lib dirs to \"${FFMPEG_${component}_LIBRARY_DIRS}\"")
- message("set lib otps not to \"${FFMPEG_${component}_LDFLAGS}\"")
+ if((TARGET PkgConfig::FFMPEG_${component}) AND (NOT CMAKE_VERSION VERSION_LESS "3.11.0"))
+ if(APPLE)
+ join(FFMPEG_LDFLAGS_STRING " " ${FFMPEG_${component}_LDFLAGS})
+ string(REGEX REPLACE "-Wl,-framework,([^ ]+)" "-framework \\1" FFMPEG_LDFLAGS_STRING_CLEAN ${FFMPEG_LDFLAGS_STRING})
+ string(REGEX MATCHALL "-framework [^ ]+" FFMPEG_FRAMEWORKS ${FFMPEG_LDFLAGS_STRING_CLEAN})
+ list(APPEND FFMPEG_${component}_LIBRARIES ${FFMPEG_FRAMEWORKS})
+ set_target_properties(PkgConfig::FFMPEG_${component} PROPERTIES
+ INTERFACE_LINK_DIRECTORIES "${FFMPEG_${component}_LIBRARY_DIRS}"
+ INTERFACE_LINK_LIBRARIES "${FFMPEG_${component}_LIBRARIES}"
+ INTERFACE_LINK_OPTIONS "")
+ message("set libs to \"${FFMPEG_${component}_LIBRARIES}\"")
+ message("set lib dirs to \"${FFMPEG_${component}_LIBRARY_DIRS}\"")
+ message("set lib otps not to \"${FFMPEG_${component}_LDFLAGS}\"")
+ endif()
+ set_target_properties(PkgConfig::FFMPEG_${component} PROPERTIES IMPORTED_GLOBAL TRUE)
+ add_library(FFMPEG::${component} ALIAS PkgConfig::FFMPEG_${component})
+ else()
+ add_library("FFMPEG::${component}" INTERFACE IMPORTED)
+ if(CMAKE_VERSION VERSION_LESS "3.6")
+ link_directories("${FFMPEG_${component}_LIBRARY_DIRS}")
+ endif()
+ set_target_properties("FFMPEG::${component}" PROPERTIES
+ INTERFACE_LINK_DIRECTORIES "${FFMPEG_${component}_LIBRARY_DIRS}"
+ INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIRS}"
+ INTERFACE_LINK_LIBRARIES "${FFMPEG_${component}_LIBRARIES}")
endif()
- set_target_properties(PkgConfig::FFMPEG_${component} PROPERTIES IMPORTED_GLOBAL TRUE)
- add_library(FFMPEG::${component} ALIAS PkgConfig::FFMPEG_${component})
return()
endif()
endif()
@@ -211,10 +231,14 @@ foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS)
list(APPEND _ffmpeg_required_vars
"FFMPEG_${_ffmpeg_component}_LIBRARIES")
else()
- set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS
- "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}")
- set(FFMPEG_${_ffmpeg_component}_LIBRARIES
- "${FFMPEG_${_ffmpeg_component}_LIBRARY}")
+ if(NOT FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS)
+ set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS
+ "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}")
+ endif()
+ if(NOT FFMPEG_${_ffmpeg_component}_LIBRARIES)
+ set(FFMPEG_${_ffmpeg_component}_LIBRARIES
+ "${FFMPEG_${_ffmpeg_component}_LIBRARY}")
+ endif()
list(APPEND FFMPEG_INCLUDE_DIRS
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}")
list(APPEND FFMPEG_LIBRARIES
diff --git a/cmake/FindILClient.cmake b/cmake/FindILClient.cmake
new file mode 100644
index 0000000..b24fccd
--- /dev/null
+++ b/cmake/FindILClient.cmake
@@ -0,0 +1,57 @@
+# Provides ILClient::ILClient
+# (Raspberry Pi-specific video decoding stuff, very specific for libraspberrypi0 and libraspberrypi-doc)
+
+set(_required_libs
+ /opt/vc/lib/libbcm_host.so
+ /opt/vc/lib/libvcilcs.a
+ /opt/vc/lib/libvchiq_arm.so
+ /opt/vc/lib/libvcos.so)
+
+unset(_libvars)
+foreach(_lib ${_required_libs})
+ get_filename_component(_libname "${_lib}" NAME_WE)
+ set(_libvar "ILClient_${_libname}_LIBRARY")
+ list(APPEND _libvars "${_libvar}")
+ if(EXISTS "${_lib}")
+ set("${_libvar}" "${_lib}")
+ else()
+ set("${_libvar}" "${_libvar}-NOTFOUND")
+ endif()
+endforeach()
+
+find_path(ILClient_INCLUDE_DIR bcm_host.h
+ PATHS /opt/vc/include
+ NO_DEFAULT_PATH)
+
+find_path(ILClient_SOURCE_DIR ilclient.c
+ PATHS /opt/vc/src/hello_pi/libs/ilclient)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(ILClient
+ FOUND_VAR ILClient_FOUND
+ REQUIRED_VARS
+ ${_libvars}
+ ILClient_INCLUDE_DIR
+ ILClient_SOURCE_DIR)
+
+if(ILClient_FOUND)
+ if(NOT TARGET ILClient::ILClient)
+ # see /opt/vc/src/hello_pi/libs/ilclient/Makefile
+ add_library(ilclient STATIC
+ "${ILClient_SOURCE_DIR}/ilclient.c"
+ "${ILClient_SOURCE_DIR}/ilcore.c")
+ target_include_directories(ilclient PUBLIC
+ "${ILClient_INCLUDE_DIR}"
+ "${ILClient_SOURCE_DIR}")
+ target_compile_definitions(ilclient PUBLIC
+ HAVE_LIBOPENMAX=2
+ OMX
+ OMX_SKIP64BIT
+ USE_EXTERNAL_OMX
+ HAVE_LIBBCM_HOST
+ USE_EXTERNAL_LIBBCM_HOST
+ USE_VCHIQ_ARM)
+ target_link_libraries(ilclient PUBLIC ${_required_libs})
+ add_library(ILClient::ILClient ALIAS ilclient)
+ endif()
+endif()
diff --git a/cmake/FindJerasure.cmake b/cmake/FindJerasure.cmake
new file mode 100644
index 0000000..973b5a6
--- /dev/null
+++ b/cmake/FindJerasure.cmake
@@ -0,0 +1,26 @@
+# Provides Jerasure::Jerasure
+
+find_path(Jerasure_INCLUDE_DIR NAMES jerasure.h)
+find_path(Jerasure_INCLUDE_DIR2 NAMES galois.h PATH_SUFFIXES jerasure)
+find_library(Jerasure_LIBRARY NAMES Jerasure)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Jerasure
+ FOUND_VAR Jerasure_FOUND
+ REQUIRED_VARS
+ Jerasure_LIBRARY
+ Jerasure_INCLUDE_DIR
+ Jerasure_INCLUDE_DIR2
+)
+
+if(Jerasure_FOUND)
+ set(Jerasure_LIBRARIES ${Jerasure_LIBRARY})
+ set(Jerasure_INCLUDE_DIRS ${Jerasure_INCLUDE_DIR} ${Jerasure_INCLUDE_DIR2})
+ if(NOT TARGET Jerasure::Jerasure)
+ add_library(Jerasure::Jerasure UNKNOWN IMPORTED)
+ set_target_properties(Jerasure::Jerasure PROPERTIES
+ IMPORTED_LOCATION "${Jerasure_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${Jerasure_INCLUDE_DIRS}"
+ )
+ endif()
+endif()
diff --git a/cmake/FindNanopb.cmake b/cmake/FindNanopb.cmake
new file mode 100644
index 0000000..ed923f2
--- /dev/null
+++ b/cmake/FindNanopb.cmake
@@ -0,0 +1,18 @@
+# Provides Nanopb::nanopb and NANOPB_GENERATOR_PY
+
+find_package(nanopb CONFIG)
+find_file(NANOPB_GENERATOR_PY nanopb_generator.py PATH_SUFFIXES bin)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Nanopb
+ FOUND_VAR Nanopb_FOUND
+ REQUIRED_VARS
+ nanopb_FOUND
+ NANOPB_GENERATOR_PY
+)
+
+if(Nanopb_FOUND)
+ if(NOT TARGET Nanopb::nanopb)
+ add_library(Nanopb::nanopb ALIAS nanopb::protobuf-nanopb-static)
+ endif()
+endif()
diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake
index a0b9ebd..03717b0 100644
--- a/cmake/FindSDL2.cmake
+++ b/cmake/FindSDL2.cmake
@@ -1,6 +1,26 @@
find_package(SDL2 NO_MODULE QUIET)
+# Adapted from libsdl-org/SDL_ttf: https://github.com/libsdl-org/SDL_ttf/blob/main/cmake/FindPrivateSDL2.cmake#L19-L31
+# Copyright (C) 1997-2022 Sam Lantinga
+# Licensed under the zlib license (https://github.com/libsdl-org/SDL_ttf/blob/main/LICENSE.txt)
+set(SDL2_VERSION_MAJOR)
+set(SDL2_VERSION_MINOR)
+set(SDL2_VERSION_PATCH)
+set(SDL2_VERSION)
+if(SDL2_INCLUDE_DIR)
+ file(READ "${SDL2_INCLUDE_DIR}/SDL_version.h" _sdl_version_h)
+ string(REGEX MATCH "#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)" _sdl2_major_re "${_sdl_version_h}")
+ set(SDL2_VERSION_MAJOR "${CMAKE_MATCH_1}")
+ string(REGEX MATCH "#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)" _sdl2_minor_re "${_sdl_version_h}")
+ set(SDL2_VERSION_MINOR "${CMAKE_MATCH_1}")
+ string(REGEX MATCH "#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)" _sdl2_patch_re "${_sdl_version_h}")
+ set(SDL2_VERSION_PATCH "${CMAKE_MATCH_1}")
+ if(_sdl2_major_re AND _sdl2_minor_re AND _sdl2_patch_re)
+ set(SDL2_VERSION "${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}")
+ endif()
+endif()
+
if(SDL2_FOUND AND (NOT TARGET SDL2::SDL2))
add_library(SDL2::SDL2 UNKNOWN IMPORTED GLOBAL)
if(NOT SDL2_LIBDIR)
diff --git a/cmake/OpenSSLExternalProject.cmake b/cmake/OpenSSLExternalProject.cmake
index 0538d48..1e1370d 100644
--- a/cmake/OpenSSLExternalProject.cmake
+++ b/cmake/OpenSSLExternalProject.cmake
@@ -33,8 +33,8 @@ endif()
find_program(MAKE_EXE NAMES gmake make)
ExternalProject_Add(OpenSSL-ExternalProject
- URL https://www.openssl.org/source/openssl-1.1.1d.tar.gz
- URL_HASH SHA256=1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2
+ URL https://www.openssl.org/source/openssl-1.1.1s.tar.gz
+ URL_HASH SHA256=c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa
INSTALL_DIR "${OPENSSL_INSTALL_DIR}"
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${OPENSSL_BUILD_ENV}
"/Configure" "--prefix=" no-shared ${OPENSSL_CONFIG_EXTRA_ARGS} "${OPENSSL_OS_COMPILER}"
diff --git a/cmake/switch.cmake b/cmake/switch.cmake
new file mode 100644
index 0000000..7e600be
--- /dev/null
+++ b/cmake/switch.cmake
@@ -0,0 +1,62 @@
+
+# Find DEVKITPRO
+set(DEVKITPRO "$ENV{DEVKITPRO}" CACHE PATH "Path to DevKitPro")
+if(NOT DEVKITPRO)
+ message(FATAL_ERROR "Please set DEVKITPRO env before calling cmake. https://devkitpro.org/wiki/Getting_Started")
+endif()
+
+# include devkitpro toolchain
+include("${DEVKITPRO}/cmake/Switch.cmake")
+
+set(NSWITCH TRUE)
+
+# troubleshoot
+message(STATUS "CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}")
+message(STATUS "PKG_CONFIG_EXECUTABLE = ${PKG_CONFIG_EXECUTABLE}")
+message(STATUS "CMAKE_EXE_LINKER_FLAGS = ${CMAKE_EXE_LINKER_FLAGS}")
+get_property(include_directories DIRECTORY PROPERTY INCLUDE_DIRECTORIES)
+message(STATUS "INCLUDE_DIRECTORIES = ${include_directories}")
+message(STATUS "CMAKE_C_COMPILER = ${CMAKE_C_COMPILER}")
+message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}")
+
+find_program(ELF2NRO elf2nro ${DEVKITPRO}/tools/bin)
+if (ELF2NRO)
+ message(STATUS "elf2nro: ${ELF2NRO} - found")
+else()
+ message(WARNING "elf2nro - not found")
+endif()
+
+find_program(NACPTOOL nacptool ${DEVKITPRO}/tools/bin)
+if (NACPTOOL)
+ message(STATUS "nacptool: ${NACPTOOL} - found")
+else()
+ message(WARNING "nacptool - not found")
+endif()
+
+function(__add_nacp target APP_TITLE APP_AUTHOR APP_VERSION)
+ set(__NACP_COMMAND ${NACPTOOL} --create ${APP_TITLE} ${APP_AUTHOR} ${APP_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/${target})
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}
+ COMMAND ${__NACP_COMMAND}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+ VERBATIM
+ )
+endfunction()
+
+function(add_nro_target output_name target title author version icon romfs)
+ if(NOT ${output_name}.nacp)
+ __add_nacp(${output_name}.nacp ${title} ${author} ${version})
+ endif()
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${output_name}.nro
+ COMMAND ${ELF2NRO} $
+ ${CMAKE_CURRENT_BINARY_DIR}/${output_name}.nro
+ --icon=${icon}
+ --nacp=${CMAKE_CURRENT_BINARY_DIR}/${output_name}.nacp
+ --romfsdir=${romfs}
+ DEPENDS ${target} ${CMAKE_CURRENT_BINARY_DIR}/${output_name}.nacp
+ VERBATIM
+ )
+ add_custom_target(${output_name}_nro ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${output_name}.nro)
+endfunction()
+
+set(CMAKE_USE_SYSTEM_ENVIRONMENT_PATH OFF)
diff --git a/doc/platform-build.md b/doc/platform-build.md
new file mode 100644
index 0000000..942cc6b
--- /dev/null
+++ b/doc/platform-build.md
@@ -0,0 +1,30 @@
+
+# Platform-specific build instructions
+
+## Fedora
+
+On Fedora, build dependencies can be installed via:
+
+```
+sudo dnf install cmake make qt5-qtmultimedia-devel qt5-qtsvg-devel qt5-qtbase-gui ffmpeg-devel opus-devel openssl-devel python3-protobuf protobuf-c protobuf-devel qt5-rpm-macros SDL2-devel libevdev-devel systemd-devel
+```
+
+Then, Chiaki builds just like any other CMake project:
+```
+git submodule update --init
+mkdir build && cd build
+cmake ..
+make
+```
+
+In order to utilize hardware decoding, necessary VA-API component needs to be installed separately depending on your GPU. For example on Fedora:
+
+* **Intel**: `libva-intel-driver`(majority laptop and desktop) OR `libva-intel-hybrid-driver`(most netbook with Atom processor)
+* **AMD**: Already part of default installation
+* **Nvidia**: `libva-vdpau-driver`
+
+## Windows
+
+Windows support is reduced to the absolute minimum for maintainability.
+Official Windows builds are done on AppVeyor within MSYS2 using this script, which can also work as a template for building locally: [scripts/appveyor.sh](../scripts/appveyor.sh).
+Other methods of building may work, but will not be officially supported.
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
index 98aa3a3..443150f 100644
--- a/gui/CMakeLists.txt
+++ b/gui/CMakeLists.txt
@@ -2,26 +2,15 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
-find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Multimedia OpenGL Svg)
+find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Concurrent Multimedia OpenGL Svg)
if(APPLE)
find_package(Qt5 REQUIRED COMPONENTS MacExtras)
endif()
-if(CHIAKI_GUI_ENABLE_QT_GAMEPAD AND CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
- message(FATAL_ERROR "Only one input method may be enabled. Disable either CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER or CHIAKI_GUI_ENABLE_QT_GAMEPAD.")
-endif()
-if(CHIAKI_GUI_ENABLE_QT_GAMEPAD)
- find_package(Qt5 REQUIRED COMPONENTS Gamepad)
-endif()
-if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
- find_package(SDL2 MODULE REQUIRED)
-endif()
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
endif()
-find_package(FFMPEG REQUIRED COMPONENTS avcodec avutil)
-
set(RESOURCE_FILES "")
if(APPLE)
@@ -34,8 +23,6 @@ add_executable(chiaki WIN32
src/main.cpp
include/streamwindow.h
src/streamwindow.cpp
- include/videodecoder.h
- src/videodecoder.cpp
include/mainwindow.h
src/mainwindow.cpp
include/dynamicgridwidget.h
@@ -60,6 +47,8 @@ add_executable(chiaki WIN32
src/registdialog.cpp
include/host.h
src/host.cpp
+ include/settingskeycapturedialog.h
+ src/settingskeycapturedialog.cpp
include/settingsdialog.h
src/settingsdialog.cpp
include/manualhostdialog.h
@@ -77,27 +66,29 @@ if(CHIAKI_ENABLE_CLI)
target_link_libraries(chiaki chiaki-cli-lib)
endif()
-target_link_libraries(chiaki FFMPEG::avcodec FFMPEG::avutil)
-target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Multimedia Qt5::OpenGL Qt5::Svg)
+target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg)
+target_link_libraries(chiaki SDL2::SDL2)
if(APPLE)
target_link_libraries(chiaki Qt5::MacExtras)
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_MACEXTRAS)
endif()
-if(CHIAKI_GUI_ENABLE_QT_GAMEPAD)
- target_link_libraries(chiaki Qt5::Gamepad)
- target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_GAMEPAD)
-endif()
if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
- target_link_libraries(chiaki SDL2::SDL2)
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
endif()
+if(CHIAKI_ENABLE_SETSU)
+ target_link_libraries(chiaki setsu)
+ target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SETSU)
+endif()
+if(NOT CHIAKI_ENABLE_FFMPEG_DECODER)
+ message(FATAL_ERROR "Chiaki GUI requires CHIAKI_ENABLE_FFMPEG_DECODER=ON")
+endif()
set_target_properties(chiaki PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist.in"
MACOSX_BUNDLE_BUNDLE_NAME Chiaki
MACOSX_BUNDLE_BUNDLE_VERSION ${CHIAKI_VERSION}
- MACOSX_BUNDLE_COPYRIGHT "thestr4ng3r (GPLv3)"
+ MACOSX_BUNDLE_COPYRIGHT "thestr4ng3r (AGPLv3)"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.chiaki.chiaki"
MACOSX_BUNDLE_ICON_FILE chiaki.icns
RESOURCE "${RESOURCE_FILES}")
@@ -106,4 +97,5 @@ install(TARGETS chiaki
RUNTIME DESTINATION bin
BUNDLE DESTINATION bin)
install(FILES chiaki.desktop DESTINATION share/applications)
-install(FILES chiaki.png DESTINATION share/icons/hicolor/512x512)
+install(FILES chiaki.png DESTINATION share/icons/hicolor/512x512/apps)
+install(FILES re.chiaki.Chiaki.appdata.xml DESTINATION share/metainfo)
diff --git a/gui/chiaki.desktop b/gui/chiaki.desktop
index 6ee0326..53f3be2 100644
--- a/gui/chiaki.desktop
+++ b/gui/chiaki.desktop
@@ -1,7 +1,7 @@
[Desktop Entry]
Type=Application
Name=Chiaki
-Comment=PlayStation 4 Remote Play Client
+Comment=PlayStation Remote Play Client
Exec=chiaki
Icon=chiaki
-Categories=Game;
\ No newline at end of file
+Categories=Game;
diff --git a/gui/chiaki.icns b/gui/chiaki.icns
index 13613ff..e51fa65 100644
Binary files a/gui/chiaki.icns and b/gui/chiaki.icns differ
diff --git a/gui/include/avopenglframeuploader.h b/gui/include/avopenglframeuploader.h
index ff7e0d0..1e37054 100644
--- a/gui/include/avopenglframeuploader.h
+++ b/gui/include/avopenglframeuploader.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_AVOPENGLFRAMEUPLOADER_H
#define CHIAKI_AVOPENGLFRAMEUPLOADER_H
@@ -21,8 +6,10 @@
#include
#include
+#include
+
+class StreamSession;
class AVOpenGLWidget;
-class VideoDecoder;
class QSurface;
class AVOpenGLFrameUploader: public QObject
@@ -30,16 +17,16 @@ class AVOpenGLFrameUploader: public QObject
Q_OBJECT
private:
- VideoDecoder *decoder;
+ StreamSession *session;
AVOpenGLWidget *widget;
QOpenGLContext *context;
QSurface *surface;
private slots:
- void UpdateFrame();
+ void UpdateFrameFromDecoder();
public:
- AVOpenGLFrameUploader(VideoDecoder *decoder, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface);
+ AVOpenGLFrameUploader(StreamSession *session, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface);
};
#endif // CHIAKI_AVOPENGLFRAMEUPLOADER_H
diff --git a/gui/include/avopenglwidget.h b/gui/include/avopenglwidget.h
index 9583b4d..19bd287 100644
--- a/gui/include/avopenglwidget.h
+++ b/gui/include/avopenglwidget.h
@@ -1,23 +1,10 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_AVOPENGLWIDGET_H
#define CHIAKI_AVOPENGLWIDGET_H
+#include "transformmode.h"
+
#include
#include
@@ -28,15 +15,37 @@ extern "C"
#include
}
-class VideoDecoder;
+#define MAX_PANES 3
+
+class StreamSession;
class AVOpenGLFrameUploader;
+class QOffscreenSurface;
+
+struct PlaneConfig
+{
+ unsigned int width_divider;
+ unsigned int height_divider;
+ unsigned int data_per_pixel;
+ GLint internal_format;
+ GLenum format;
+};
+
+struct ConversionConfig
+{
+ enum AVPixelFormat pixel_format;
+ const char *shader_vert_glsl;
+ const char *shader_frag_glsl;
+ unsigned int planes;
+ struct PlaneConfig plane_configs[MAX_PANES];
+};
struct AVOpenGLFrame
{
- GLuint pbo[3];
- GLuint tex[3];
+ GLuint pbo[MAX_PANES];
+ GLuint tex[MAX_PANES];
unsigned int width;
unsigned int height;
+ ConversionConfig *conversion_config;
bool Update(AVFrame *frame, ChiakiLog *log);
};
@@ -46,7 +55,7 @@ class AVOpenGLWidget: public QOpenGLWidget
Q_OBJECT
private:
- VideoDecoder *decoder;
+ StreamSession *session;
GLuint program;
GLuint vbo;
@@ -55,22 +64,37 @@ class AVOpenGLWidget: public QOpenGLWidget
AVOpenGLFrame frames[2];
int frame_fg;
QMutex frames_mutex;
+ QOffscreenSurface *frame_uploader_surface;
QOpenGLContext *frame_uploader_context;
AVOpenGLFrameUploader *frame_uploader;
QThread *frame_uploader_thread;
+ QTimer *mouse_timer;
+
+ ConversionConfig *conversion_config;
+
public:
static QSurfaceFormat CreateSurfaceFormat();
- explicit AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent = nullptr);
+ explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr, TransformMode transform_mode = TransformMode::Fit);
~AVOpenGLWidget() override;
void SwapFrames();
AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; }
+ void SetTransformMode(TransformMode mode) { transform_mode = mode; }
+ TransformMode GetTransformMode() const { return transform_mode; }
+
protected:
+ TransformMode transform_mode;
+ void mouseMoveEvent(QMouseEvent *event) override;
+
void initializeGL() override;
void paintGL() override;
+
+ public slots:
+ void ResetMouseTimeout();
+ void HideMouse();
};
#endif // CHIAKI_AVOPENGLWIDGET_H
diff --git a/gui/include/controllermanager.h b/gui/include/controllermanager.h
index 952275e..eee0120 100644
--- a/gui/include/controllermanager.h
+++ b/gui/include/controllermanager.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_CONTROLLERMANAGER_H
#define CHIAKI_CONTROLLERMANAGER_H
@@ -27,8 +12,12 @@
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
#include
+#include
#endif
+#define PS_TOUCHPAD_MAX_X 1920
+#define PS_TOUCHPAD_MAX_Y 1079
+
class Controller;
class ControllerManager : public QObject
@@ -48,7 +37,9 @@ class ControllerManager : public QObject
private slots:
void UpdateAvailableControllers();
void HandleEvents();
- void ControllerEvent(int device_id);
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ void ControllerEvent(SDL_Event evt);
+#endif
public:
static ControllerManager *GetInstance();
@@ -56,7 +47,7 @@ class ControllerManager : public QObject
ControllerManager(QObject *parent = nullptr);
~ControllerManager();
- QList GetAvailableControllers();
+ QSet GetAvailableControllers();
Controller *OpenController(int device_id);
signals:
@@ -72,12 +63,24 @@ class Controller : public QObject
private:
Controller(int device_id, ControllerManager *manager);
- void UpdateState();
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ void UpdateState(SDL_Event event);
+ bool HandleButtonEvent(SDL_ControllerButtonEvent event);
+ bool HandleAxisEvent(SDL_ControllerAxisEvent event);
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ bool HandleSensorEvent(SDL_ControllerSensorEvent event);
+ bool HandleTouchpadEvent(SDL_ControllerTouchpadEvent event);
+#endif
+#endif
ControllerManager *manager;
int id;
+ ChiakiOrientationTracker orientation_tracker;
+ ChiakiControllerState state;
+ bool is_dualsense;
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ QMap, uint8_t> touch_ids;
SDL_GameController *controller;
#endif
@@ -88,9 +91,44 @@ class Controller : public QObject
int GetDeviceID();
QString GetName();
ChiakiControllerState GetState();
+ void SetRumble(uint8_t left, uint8_t right);
+ void SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right);
+ bool IsDualSense();
signals:
void StateChanged();
};
+/* PS5 trigger effect documentation:
+ https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
+
+ Taken from SDL2, licensed under the zlib license,
+ Copyright (C) 1997-2022 Sam Lantinga
+ https://github.com/libsdl-org/SDL/blob/release-2.24.1/test/testgamecontroller.c#L263-L289
+*/
+typedef struct
+{
+ Uint8 ucEnableBits1; /* 0 */
+ Uint8 ucEnableBits2; /* 1 */
+ Uint8 ucRumbleRight; /* 2 */
+ Uint8 ucRumbleLeft; /* 3 */
+ Uint8 ucHeadphoneVolume; /* 4 */
+ Uint8 ucSpeakerVolume; /* 5 */
+ Uint8 ucMicrophoneVolume; /* 6 */
+ Uint8 ucAudioEnableBits; /* 7 */
+ Uint8 ucMicLightMode; /* 8 */
+ Uint8 ucAudioMuteBits; /* 9 */
+ Uint8 rgucRightTriggerEffect[11]; /* 10 */
+ Uint8 rgucLeftTriggerEffect[11]; /* 21 */
+ Uint8 rgucUnknown1[6]; /* 32 */
+ Uint8 ucLedFlags; /* 38 */
+ Uint8 rgucUnknown2[2]; /* 39 */
+ Uint8 ucLedAnim; /* 41 */
+ Uint8 ucLedBrightness; /* 42 */
+ Uint8 ucPadLights; /* 43 */
+ Uint8 ucLedRed; /* 44 */
+ Uint8 ucLedGreen; /* 45 */
+ Uint8 ucLedBlue; /* 46 */
+} DS5EffectsState_t;
+
#endif // CHIAKI_CONTROLLERMANAGER_H
diff --git a/gui/include/discoverymanager.h b/gui/include/discoverymanager.h
index 05cb0f7..be3462c 100644
--- a/gui/include/discoverymanager.h
+++ b/gui/include/discoverymanager.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_DISCOVERYMANAGER_H
#define CHIAKI_DISCOVERYMANAGER_H
@@ -27,6 +12,7 @@
struct DiscoveryHost
{
+ bool ps5;
ChiakiDiscoveryHostState state;
uint16_t host_request_port;
#define STRING_MEMBER(name) QString name;
@@ -59,7 +45,7 @@ class DiscoveryManager : public QObject
void SetActive(bool active);
- void SendWakeup(const QString &host, const QByteArray ®ist_key);
+ void SendWakeup(const QString &host, const QByteArray ®ist_key, bool ps5);
const QList GetHosts() const { return hosts; }
diff --git a/gui/include/dynamicgridwidget.h b/gui/include/dynamicgridwidget.h
index efae63f..6a9330d 100644
--- a/gui/include/dynamicgridwidget.h
+++ b/gui/include/dynamicgridwidget.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_DYNAMICGRIDWIDGET_H
#define CHIAKI_DYNAMICGRIDWIDGET_H
diff --git a/gui/include/exception.h b/gui/include/exception.h
index 7d0a615..2ffe45c 100644
--- a/gui/include/exception.h
+++ b/gui/include/exception.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_EXCEPTION_H
#define CHIAKI_EXCEPTION_H
diff --git a/gui/include/host.h b/gui/include/host.h
index 7d143f4..8ff292e 100644
--- a/gui/include/host.h
+++ b/gui/include/host.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_HOST_H
#define CHIAKI_HOST_H
@@ -53,12 +38,13 @@ static bool operator<(const HostMAC &a, const HostMAC &b) { return a.GetValue()
class RegisteredHost
{
private:
+ ChiakiTarget target;
QString ap_ssid;
QString ap_bssid;
QString ap_key;
QString ap_name;
- HostMAC ps4_mac;
- QString ps4_nickname;
+ HostMAC server_mac;
+ QString server_nickname;
char rp_regist_key[CHIAKI_SESSION_AUTH_SIZE];
uint32_t rp_key_type;
uint8_t rp_key[0x10];
@@ -69,8 +55,9 @@ class RegisteredHost
RegisteredHost(const ChiakiRegisteredHost &chiaki_host);
- const HostMAC &GetPS4MAC() const { return ps4_mac; }
- const QString &GetPS4Nickname() const { return ps4_nickname; }
+ ChiakiTarget GetTarget() const { return target; }
+ const HostMAC &GetServerMAC() const { return server_mac; }
+ const QString &GetServerNickname() const { return server_nickname; }
const QByteArray GetRPRegistKey() const { return QByteArray(rp_regist_key, sizeof(rp_regist_key)); }
const QByteArray GetRPKey() const { return QByteArray((const char *)rp_key, sizeof(rp_key)); }
@@ -96,7 +83,7 @@ class ManualHost
bool GetRegistered() const { return registered; }
HostMAC GetMAC() const { return registered_mac; }
- void Register(const RegisteredHost ®istered_host) { this->registered = true; this->registered_mac = registered_host.GetPS4MAC(); }
+ void Register(const RegisteredHost ®istered_host) { this->registered = true; this->registered_mac = registered_host.GetServerMAC(); }
void SaveToSettings(QSettings *settings) const;
static ManualHost LoadFromSettings(QSettings *settings);
diff --git a/gui/include/loginpindialog.h b/gui/include/loginpindialog.h
index fc6d0e5..cc3a411 100644
--- a/gui/include/loginpindialog.h
+++ b/gui/include/loginpindialog.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_LOGINPINDIALOG_H
#define CHIAKI_LOGINPINDIALOG_H
diff --git a/gui/include/logsniffer.h b/gui/include/logsniffer.h
new file mode 100644
index 0000000..aa398a0
--- /dev/null
+++ b/gui/include/logsniffer.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: LicenseRef-GPL-3.0-or-later-OpenSSL
+
+#ifndef CHIAKI_LOG_SNIFFER_H
+#define CHIAKI_LOG_SNIFFER_H
+
+#include
+
+#include
+#include
+#include
+#include
+
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+#include
+#endif
+
+class Controller;
+
+class ControllerManager : public QObject
+{
+ Q_OBJECT
+
+ friend class Controller;
+
+ private:
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ QSet available_controllers;
+#endif
+ QMap open_controllers;
+
+ void ControllerClosed(Controller *controller);
+
+ private slots:
+ void UpdateAvailableControllers();
+ void HandleEvents();
+ void ControllerEvent(int device_id);
+
+ public:
+ static ControllerManager *GetInstance();
+
+ ControllerManager(QObject *parent = nullptr);
+ ~ControllerManager();
+
+ QSet GetAvailableControllers();
+ Controller *OpenController(int device_id);
+
+ signals:
+ void AvailableControllersUpdated();
+};
+
+class Controller : public QObject
+{
+ Q_OBJECT
+
+ friend class ControllerManager;
+
+ private:
+ Controller(int device_id, ControllerManager *manager);
+
+ void UpdateState();
+
+ ControllerManager *manager;
+ int id;
+
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ SDL_GameController *controller;
+#endif
+
+ public:
+ ~Controller();
+
+ bool IsConnected();
+ int GetDeviceID();
+ QString GetName();
+ ChiakiControllerState GetState();
+
+ signals:
+ void StateChanged();
+};
+
+#endif // CHIAKI_CONTROLLERMANAGER_H
diff --git a/gui/include/mainwindow.h b/gui/include/mainwindow.h
index cb287b2..4112c19 100644
--- a/gui/include/mainwindow.h
+++ b/gui/include/mainwindow.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_MAINWINDOW_H
#define CHIAKI_MAINWINDOW_H
@@ -37,6 +22,8 @@ struct DisplayServer
bool registered;
QString GetHostAddr() const { return discovered ? discovery_host.host_addr : manual_host.GetHost(); }
+ bool IsPS5() const { return discovered ? discovery_host.ps5 :
+ (registered ? chiaki_target_is_ps5(registered_host.GetTarget()) : false); }
};
class MainWindow : public QMainWindow
@@ -68,6 +55,7 @@ class MainWindow : public QMainWindow
void UpdateDiscoveryEnabled();
void ShowSettings();
+ void Quit();
void UpdateDisplayServers();
void UpdateServerWidgets();
diff --git a/gui/include/manualhostdialog.h b/gui/include/manualhostdialog.h
index eeb3c03..b56cc8e 100644
--- a/gui/include/manualhostdialog.h
+++ b/gui/include/manualhostdialog.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_MANUALHOSTDIALOG_H
#define CHIAKI_MANUALHOSTDIALOG_H
diff --git a/gui/include/registdialog.h b/gui/include/registdialog.h
index d159960..7d16b0f 100644
--- a/gui/include/registdialog.h
+++ b/gui/include/registdialog.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_REGISTDIALOG_H
#define CHIAKI_REGISTDIALOG_H
@@ -41,9 +26,11 @@ class RegistDialog : public QDialog
QLineEdit *host_edit;
QCheckBox *broadcast_check_box;
- QRadioButton *psn_online_id_radio_button;
+ QRadioButton *ps4_pre9_radio_button;
+ QRadioButton *ps4_pre10_radio_button;
+ QRadioButton *ps4_10_radio_button;
+ QRadioButton *ps5_radio_button;
QLineEdit *psn_online_id_edit;
- QRadioButton *psn_account_id_radio_button;
QLineEdit *psn_account_id_edit;
QLineEdit *pin_edit;
QDialogButtonBox *button_box;
@@ -51,6 +38,8 @@ class RegistDialog : public QDialog
RegisteredHost registered_host;
+ bool NeedAccountId();
+
private slots:
void ValidateInput();
diff --git a/gui/include/servericonwidget.h b/gui/include/servericonwidget.h
index 0a714b4..364cb4b 100644
--- a/gui/include/servericonwidget.h
+++ b/gui/include/servericonwidget.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SERVERICONWIDGET_H
#define CHIAKI_SERVERICONWIDGET_H
@@ -21,13 +6,18 @@
#include
#include
+#include
class ServerIconWidget : public QWidget
{
Q_OBJECT
private:
+ bool ps5 = false;
ChiakiDiscoveryHostState state = CHIAKI_DISCOVERY_HOST_STATE_UNKNOWN;
+ QSvgRenderer svg_renderer;
+
+ void LoadSvg();
protected:
void paintEvent(QPaintEvent *event) override;
@@ -35,7 +25,7 @@ class ServerIconWidget : public QWidget
public:
explicit ServerIconWidget(QWidget *parent = nullptr);
- void SetState(ChiakiDiscoveryHostState state) { this->state = state; update(); }
+ void SetState(bool ps5, ChiakiDiscoveryHostState state);
};
#endif // CHIAKI_SERVERICONWIDGET_H
diff --git a/gui/include/serveritemwidget.h b/gui/include/serveritemwidget.h
index 76f4258..24a8ba1 100644
--- a/gui/include/serveritemwidget.h
+++ b/gui/include/serveritemwidget.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SERVERITEMWIDGET_H
#define CHIAKI_SERVERITEMWIDGET_H
diff --git a/gui/include/sessionlog.h b/gui/include/sessionlog.h
index 6977460..e498bb3 100644
--- a/gui/include/sessionlog.h
+++ b/gui/include/sessionlog.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SESSIONLOG_H
#define CHIAKI_SESSIONLOG_H
diff --git a/gui/include/settings.h b/gui/include/settings.h
index 71cff6c..00f38ab 100644
--- a/gui/include/settings.h
+++ b/gui/include/settings.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SETTINGS_H
#define CHIAKI_SETTINGS_H
@@ -23,6 +8,33 @@
#include "host.h"
#include
+#include
+
+enum class ControllerButtonExt
+{
+ // must not overlap with ChiakiControllerButton and ChiakiControllerAnalogButton
+ ANALOG_STICK_LEFT_X_UP = (1 << 18),
+ ANALOG_STICK_LEFT_X_DOWN = (1 << 19),
+ ANALOG_STICK_LEFT_Y_UP = (1 << 20),
+ ANALOG_STICK_LEFT_Y_DOWN = (1 << 21),
+ ANALOG_STICK_RIGHT_X_UP = (1 << 22),
+ ANALOG_STICK_RIGHT_X_DOWN = (1 << 23),
+ ANALOG_STICK_RIGHT_Y_UP = (1 << 24),
+ ANALOG_STICK_RIGHT_Y_DOWN = (1 << 25),
+};
+
+enum class DisconnectAction
+{
+ AlwaysNothing,
+ AlwaysSleep,
+ Ask
+};
+
+enum class Decoder
+{
+ Ffmpeg,
+ Pi
+};
class Settings : public QObject
{
@@ -51,6 +63,9 @@ class Settings : public QObject
void SetLogVerbose(bool enabled) { settings.setValue("settings/log_verbose", enabled); }
uint32_t GetLogLevelMask();
+ bool GetDualSenseEnabled() const { return settings.value("settings/dualsense_enabled", false).toBool(); }
+ void SetDualSenseEnabled(bool enabled) { settings.setValue("settings/dualsense_enabled", enabled); }
+
ChiakiVideoResolutionPreset GetResolution() const;
void SetResolution(ChiakiVideoResolutionPreset resolution);
@@ -63,6 +78,15 @@ class Settings : public QObject
unsigned int GetBitrate() const;
void SetBitrate(unsigned int bitrate);
+ ChiakiCodec GetCodec() const;
+ void SetCodec(ChiakiCodec codec);
+
+ Decoder GetDecoder() const;
+ void SetDecoder(Decoder decoder);
+
+ QString GetHardwareDecoder() const;
+ void SetHardwareDecoder(const QString &hw_decoder);
+
unsigned int GetAudioBufferSizeDefault() const;
/**
@@ -75,9 +99,15 @@ class Settings : public QObject
*/
unsigned int GetAudioBufferSize() const;
void SetAudioBufferSize(unsigned int size);
+
+ QString GetAudioOutDevice() const;
+ void SetAudioOutDevice(QString device_name);
ChiakiConnectVideoProfile GetVideoProfile();
+ DisconnectAction GetDisconnectAction();
+ void SetDisconnectAction(DisconnectAction action);
+
QList GetRegisteredHosts() const { return registered_hosts.values(); }
void AddRegisteredHost(const RegisteredHost &host);
void RemoveRegisteredHost(const HostMAC &mac);
@@ -90,6 +120,11 @@ class Settings : public QObject
bool GetManualHostExists(int id) { return manual_hosts.contains(id); }
ManualHost GetManualHost(int id) const { return manual_hosts[id]; }
+ static QString GetChiakiControllerButtonName(int);
+ void SetControllerButtonMapping(int, Qt::Key);
+ QMap GetControllerMapping();
+ QMap GetControllerMappingForDecoding();
+
signals:
void RegisteredHostsUpdated();
void ManualHostsUpdated();
diff --git a/gui/include/settingsdialog.h b/gui/include/settingsdialog.h
index abc5a33..00af648 100644
--- a/gui/include/settingsdialog.h
+++ b/gui/include/settingsdialog.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SETTINGSDIALOG_H
#define CHIAKI_SETTINGSDIALOG_H
@@ -34,11 +19,17 @@ class SettingsDialog : public QDialog
Settings *settings;
QCheckBox *log_verbose_check_box;
+ QComboBox *disconnect_action_combo_box;
+ QCheckBox *dualsense_check_box;
QComboBox *resolution_combo_box;
QComboBox *fps_combo_box;
QLineEdit *bitrate_edit;
+ QComboBox *codec_combo_box;
QLineEdit *audio_buffer_size_edit;
+ QComboBox *audio_device_combo_box;
+ QCheckBox *pi_decoder_check_box;
+ QComboBox *hw_decoder_combo_box;
QListWidget *registered_hosts_list_widget;
QPushButton *delete_registered_host_button;
@@ -47,11 +38,17 @@ class SettingsDialog : public QDialog
private slots:
void LogVerboseChanged();
+ void DualSenseChanged();
+ void DisconnectActionSelected();
void ResolutionSelected();
void FPSSelected();
void BitrateEdited();
+ void CodecSelected();
void AudioBufferSizeEdited();
+ void AudioOutputSelected();
+ void HardwareDecodeEngineSelected();
+ void UpdateHardwareDecodeEngineComboBox();
void UpdateRegisteredHosts();
void UpdateRegisteredHostsButtons();
diff --git a/gui/include/settingskeycapturedialog.h b/gui/include/settingskeycapturedialog.h
new file mode 100644
index 0000000..05ddd5f
--- /dev/null
+++ b/gui/include/settingskeycapturedialog.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef CHIAKI_SETTINGSKEYCAPTUREDIALOG_H
+#define CHIAKI_SETTINGSKEYCAPTUREDIALOG_H
+
+#include
+
+class SettingsKeyCaptureDialog : public QDialog
+{
+ Q_OBJECT
+
+ signals:
+ void KeyCaptured(Qt::Key);
+
+ protected:
+ void keyReleaseEvent(QKeyEvent *event) override;
+
+ public:
+ explicit SettingsKeyCaptureDialog(QWidget *parent = nullptr);
+};
+
+#endif // CHIAKI_SETTINGSKEYCAPTUREDIALOG_H
diff --git a/gui/include/streamsession.h b/gui/include/streamsession.h
index a6ca741..4b0ba01 100644
--- a/gui/include/streamsession.h
+++ b/gui/include/streamsession.h
@@ -1,37 +1,31 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_STREAMSESSION_H
#define CHIAKI_STREAMSESSION_H
#include
#include
+#include
+
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+#include
+#endif
+
+#if CHIAKI_GUI_ENABLE_SETSU
+#include
+#include
+#endif
-#include "videodecoder.h"
#include "exception.h"
#include "sessionlog.h"
#include "controllermanager.h"
+#include "settings.h"
+#include "transformmode.h"
#include
#include
-
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
-class QGamepad;
-#endif
+#include
+#include
class QAudioOutput;
class QIODevice;
@@ -46,16 +40,32 @@ class ChiakiException: public Exception
struct StreamSessionConnectInfo
{
+ Settings *settings;
+ QMap key_map;
+ Decoder decoder;
+ QString hw_decoder;
+ QString audio_out_device;
uint32_t log_level_mask;
QString log_file;
+ ChiakiTarget target;
QString host;
QByteArray regist_key;
QByteArray morning;
ChiakiConnectVideoProfile video_profile;
unsigned int audio_buffer_size;
+ bool fullscreen;
+ TransformMode transform_mode;
+ bool enable_keyboard;
+ bool enable_dualsense;
- StreamSessionConnectInfo();
- StreamSessionConnectInfo(Settings *settings, QString host, QByteArray regist_key, QByteArray morning);
+ StreamSessionConnectInfo(
+ Settings *settings,
+ ChiakiTarget target,
+ QString host,
+ QByteArray regist_key,
+ QByteArray morning,
+ bool fullscreen,
+ TransformMode transform_mode);
};
class StreamSession : public QObject
@@ -68,46 +78,72 @@ class StreamSession : public QObject
SessionLog log;
ChiakiSession session;
ChiakiOpusDecoder opus_decoder;
+ bool connected;
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- QGamepad *gamepad;
+ QHash controllers;
+#if CHIAKI_GUI_ENABLE_SETSU
+ Setsu *setsu;
+ QMap, uint8_t> setsu_ids;
+ ChiakiControllerState setsu_state;
+ SetsuDevice *setsu_motion_device;
+ ChiakiOrientationTracker orient_tracker;
+ bool orient_dirty;
#endif
- Controller *controller;
ChiakiControllerState keyboard_state;
- VideoDecoder video_decoder;
+ ChiakiFfmpegDecoder *ffmpeg_decoder;
+ void TriggerFfmpegFrameAvailable();
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ ChiakiPiDecoder *pi_decoder;
+#endif
+ QAudioDeviceInfo audio_out_device_info;
unsigned int audio_buffer_size;
QAudioOutput *audio_output;
QIODevice *audio_io;
+ SDL_AudioDeviceID haptics_output;
+ uint8_t *haptics_resampler_buf;
+
+ QMap key_map;
void PushAudioFrame(int16_t *buf, size_t samples_count);
- void PushVideoSample(uint8_t *buf, size_t buf_size);
- void Event(ChiakiEvent *event);
+ void PushHapticsFrame(uint8_t *buf, size_t buf_size);
+#if CHIAKI_GUI_ENABLE_SETSU
+ void HandleSetsuEvent(SetsuEvent *event);
+#endif
private slots:
void InitAudio(unsigned int channels, unsigned int rate);
+ void InitHaptics();
+ void Event(ChiakiEvent *event);
+ void DisconnectHaptics();
+ void ConnectHaptics();
public:
explicit StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent = nullptr);
~StreamSession();
+ bool IsConnected() { return connected; }
+
void Start();
void Stop();
+ void GoToBed();
void SetLoginPIN(const QString &pin);
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- QGamepad *GetGamepad() { return gamepad; }
+ ChiakiLog *GetChiakiLog() { return log.GetChiakiLog(); }
+ QList GetControllers() { return controllers.values(); }
+ ChiakiFfmpegDecoder *GetFfmpegDecoder() { return ffmpeg_decoder; }
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ ChiakiPiDecoder *GetPiDecoder() { return pi_decoder; }
#endif
- Controller *GetController() { return controller; }
- VideoDecoder *GetVideoDecoder() { return &video_decoder; }
void HandleKeyboardEvent(QKeyEvent *event);
+ bool HandleMouseEvent(QMouseEvent *event);
signals:
- void CurrentImageUpdated();
+ void FfmpegFrameAvailable();
void SessionQuit(ChiakiQuitReason reason, const QString &reason_str);
void LoginPINRequested(bool incorrect);
diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h
index 5e67e6e..f3bd8bb 100644
--- a/gui/include/streamwindow.h
+++ b/gui/include/streamwindow.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_GUI_STREAMWINDOW_H
#define CHIAKI_GUI_STREAMWINDOW_H
@@ -34,21 +19,36 @@ class StreamWindow: public QMainWindow
~StreamWindow() override;
private:
+ const StreamSessionConnectInfo connect_info;
StreamSession *session;
+ QAction *fullscreen_action;
+ QAction *stretch_action;
+ QAction *zoom_action;
AVOpenGLWidget *av_widget;
- void Init(const StreamSessionConnectInfo &connect_info);
+ void Init();
+ void UpdateVideoTransform();
+ void UpdateTransformModeActions();
protected:
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void closeEvent(QCloseEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void moveEvent(QMoveEvent *event) override;
+ void changeEvent(QEvent *event) override;
private slots:
void SessionQuit(ChiakiQuitReason reason, const QString &reason_str);
void LoginPINRequested(bool incorrect);
void ToggleFullscreen();
+ void ToggleStretch();
+ void ToggleZoom();
+ void Quit();
};
#endif // CHIAKI_GUI_STREAMWINDOW_H
diff --git a/gui/include/transformmode.h b/gui/include/transformmode.h
new file mode 100644
index 0000000..5c01d87
--- /dev/null
+++ b/gui/include/transformmode.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef CHIAKI_TRANSFORMMODE_H
+#define CHIAKI_TRANSFORMMODE_H
+
+enum class TransformMode {
+ Fit,
+ Zoom,
+ Stretch
+};
+
+#endif
diff --git a/gui/include/videodecoder.h b/gui/include/videodecoder.h
deleted file mode 100644
index e71e3c6..0000000
--- a/gui/include/videodecoder.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
-
-#ifndef CHIAKI_VIDEODECODER_H
-#define CHIAKI_VIDEODECODER_H
-
-#include
-
-#include "exception.h"
-
-#include
-#include
-
-extern "C"
-{
-#include
-}
-
-#include
-
-class VideoDecoderException: public Exception
-{
- public:
- explicit VideoDecoderException(const QString &msg) : Exception(msg) {};
-};
-
-class VideoDecoder: public QObject
-{
- Q_OBJECT
-
- public:
- VideoDecoder(ChiakiLog *log);
- ~VideoDecoder();
-
- void PushFrame(uint8_t *buf, size_t buf_size);
- AVFrame *PullFrame();
-
- ChiakiLog *GetChiakiLog() { return log; }
-
- signals:
- void FramesAvailable();
-
- private:
- ChiakiLog *log;
- QMutex mutex;
-
- AVCodec *codec;
- AVCodecContext *codec_context;
-};
-
-#endif // CHIAKI_VIDEODECODER_H
diff --git a/gui/re.chiaki.Chiaki.appdata.xml b/gui/re.chiaki.Chiaki.appdata.xml
new file mode 100644
index 0000000..adb10ba
--- /dev/null
+++ b/gui/re.chiaki.Chiaki.appdata.xml
@@ -0,0 +1,25 @@
+
+
+
+ re.chiaki.Chiaki
+ chiaki.desktop
+ Chiaki
+ Free and Open Source Client for PlayStation Remote Play
+ CC0-1.0
+ AGPL-3.0-only
+ Florian Märkl
+ https://chiaki.re
+ https://git.sr.ht/~thestr4ng3r/chiaki/tree/master/item/README.md#usage
+
+
+ Chiaki is a Free and Open Source Client for PlayStation 4 and PlayStation 5 Remote Play. It can be used to play in real time on a PlayStation as long as there is a network connection.
+
+
+
+
+ https://git.sr.ht/~thestr4ng3r/chiaki/blob/refs/heads/master/assets/screenshot.png
+
+
+ chiaki@metallic.software
+
+
diff --git a/gui/res/chiaki_macos.svg b/gui/res/chiaki_macos.svg
new file mode 100644
index 0000000..7f1359d
--- /dev/null
+++ b/gui/res/chiaki_macos.svg
@@ -0,0 +1,102 @@
+
+
+
+
diff --git a/gui/res/console-ps4.svg b/gui/res/console-ps4.svg
new file mode 100644
index 0000000..22882ee
--- /dev/null
+++ b/gui/res/console-ps4.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/gui/res/console-ps5.svg b/gui/res/console-ps5.svg
new file mode 100644
index 0000000..8216a04
--- /dev/null
+++ b/gui/res/console-ps5.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/gui/res/resources.qrc b/gui/res/resources.qrc
index bf7efd5..b89bee7 100644
--- a/gui/res/resources.qrc
+++ b/gui/res/resources.qrc
@@ -5,5 +5,8 @@
discover-24px.svgdiscover-off-24px.svgchiaki.svg
+ chiaki_macos.svg
+ console-ps4.svg
+ console-ps5.svg
diff --git a/gui/src/avopenglframeuploader.cpp b/gui/src/avopenglframeuploader.cpp
index 1c2905a..fd68a74 100644
--- a/gui/src/avopenglframeuploader.cpp
+++ b/gui/src/avopenglframeuploader.cpp
@@ -1,49 +1,41 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
-#include
+#include
#include
#include
-AVOpenGLFrameUploader::AVOpenGLFrameUploader(VideoDecoder *decoder, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface)
+AVOpenGLFrameUploader::AVOpenGLFrameUploader(StreamSession *session, AVOpenGLWidget *widget, QOpenGLContext *context, QSurface *surface)
: QObject(nullptr),
- decoder(decoder),
+ session(session),
widget(widget),
context(context),
surface(surface)
{
- connect(decoder, SIGNAL(FramesAvailable()), this, SLOT(UpdateFrame()));
+ connect(session, &StreamSession::FfmpegFrameAvailable, this, &AVOpenGLFrameUploader::UpdateFrameFromDecoder);
}
-void AVOpenGLFrameUploader::UpdateFrame()
+void AVOpenGLFrameUploader::UpdateFrameFromDecoder()
{
+ ChiakiFfmpegDecoder *decoder = session->GetFfmpegDecoder();
+ if(!decoder)
+ {
+ CHIAKI_LOGE(session->GetChiakiLog(), "Session has no ffmpeg decoder");
+ return;
+ }
+
if(QOpenGLContext::currentContext() != context)
context->makeCurrent(surface);
- AVFrame *next_frame = decoder->PullFrame();
+ AVFrame *next_frame = chiaki_ffmpeg_decoder_pull_frame(decoder);
if(!next_frame)
return;
- bool success = widget->GetBackgroundFrame()->Update(next_frame, decoder->GetChiakiLog());
+ bool success = widget->GetBackgroundFrame()->Update(next_frame, decoder->log);
av_frame_free(&next_frame);
if(success)
widget->SwapFrames();
-}
\ No newline at end of file
+}
diff --git a/gui/src/avopenglwidget.cpp b/gui/src/avopenglwidget.cpp
index 49f83c3..bfd184a 100644
--- a/gui/src/avopenglwidget.cpp
+++ b/gui/src/avopenglwidget.cpp
@@ -1,28 +1,17 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
-#include
#include
+#include
+#include
#include
#include
#include
#include
+#include
+
+#define MOUSE_TIMEOUT_MS 1000
//#define DEBUG_OPENGL
@@ -40,23 +29,22 @@ void main()
}
)glsl";
-static const char *shader_frag_glsl = R"glsl(
+static const char *yuv420p_shader_frag_glsl = R"glsl(
#version 150 core
-uniform sampler2D tex_y;
-uniform sampler2D tex_u;
-uniform sampler2D tex_v;
+uniform sampler2D plane1; // Y
+uniform sampler2D plane2; // U
+uniform sampler2D plane3; // V
in vec2 uv_var;
-
out vec4 out_color;
void main()
{
vec3 yuv = vec3(
- (texture(tex_y, uv_var).r - (16.0 / 255.0)) / ((235.0 - 16.0) / 255.0),
- (texture(tex_u, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5,
- (texture(tex_v, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5);
+ (texture(plane1, uv_var).r - (16.0 / 255.0)) / ((235.0 - 16.0) / 255.0),
+ (texture(plane2, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5,
+ (texture(plane3, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5);
vec3 rgb = mat3(
1.0, 1.0, 1.0,
0.0, -0.21482, 2.12798,
@@ -65,6 +53,55 @@ void main()
}
)glsl";
+static const char *nv12_shader_frag_glsl = R"glsl(
+#version 150 core
+
+uniform sampler2D plane1; // Y
+uniform sampler2D plane2; // interlaced UV
+
+in vec2 uv_var;
+
+out vec4 out_color;
+
+void main()
+{
+ vec3 yuv = vec3(
+ (texture(plane1, uv_var).r - (16.0 / 255.0)) / ((235.0 - 16.0) / 255.0),
+ (texture(plane2, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5,
+ (texture(plane2, uv_var).g - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5
+ );
+ vec3 rgb = mat3(
+ 1.0, 1.0, 1.0,
+ 0.0, -0.21482, 2.12798,
+ 1.28033, -0.38059, 0.0) * yuv;
+ out_color = vec4(rgb, 1.0);
+}
+)glsl";
+
+ConversionConfig conversion_configs[] = {
+ {
+ AV_PIX_FMT_YUV420P,
+ shader_vert_glsl,
+ yuv420p_shader_frag_glsl,
+ 3,
+ {
+ { 1, 1, 1, GL_R8, GL_RED },
+ { 2, 2, 1, GL_R8, GL_RED },
+ { 2, 2, 1, GL_R8, GL_RED }
+ }
+ },
+ {
+ AV_PIX_FMT_NV12,
+ shader_vert_glsl,
+ nv12_shader_frag_glsl,
+ 2,
+ {
+ { 1, 1, 1, GL_R8, GL_RED },
+ { 2, 2, 2, GL_RG8, GL_RG }
+ }
+ }
+};
+
static const float vert_pos[] = {
0.0f, 0.0f,
0.0f, 1.0f,
@@ -72,7 +109,6 @@ static const float vert_pos[] = {
1.0f, 1.0f
};
-
QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat()
{
QSurfaceFormat format;
@@ -86,16 +122,35 @@ QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat()
return format;
}
-AVOpenGLWidget::AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent)
+AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent, TransformMode transform_mode)
: QOpenGLWidget(parent),
- decoder(decoder)
+ session(session), transform_mode(transform_mode)
{
+ enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder());
+ conversion_config = nullptr;
+ for(auto &cc : conversion_configs)
+ {
+ if(pixel_format == cc.pixel_format)
+ {
+ conversion_config = &cc;
+ break;
+ }
+ }
+
+ if(!conversion_config)
+ throw Exception("No matching video conversion config can be found");
+
setFormat(CreateSurfaceFormat());
frame_uploader_context = nullptr;
frame_uploader = nullptr;
frame_uploader_thread = nullptr;
frame_fg = 0;
+
+ setMouseTracking(true);
+ mouse_timer = new QTimer(this);
+ connect(mouse_timer, &QTimer::timeout, this, &AVOpenGLWidget::HideMouse);
+ ResetMouseTimeout();
}
AVOpenGLWidget::~AVOpenGLWidget()
@@ -108,6 +163,24 @@ AVOpenGLWidget::~AVOpenGLWidget()
}
delete frame_uploader;
delete frame_uploader_context;
+ delete frame_uploader_surface;
+}
+
+void AVOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ QOpenGLWidget::mouseMoveEvent(event);
+ ResetMouseTimeout();
+}
+
+void AVOpenGLWidget::ResetMouseTimeout()
+{
+ unsetCursor();
+ mouse_timer->start(MOUSE_TIMEOUT_MS);
+}
+
+void AVOpenGLWidget::HideMouse()
+{
+ setCursor(Qt::BlankCursor);
}
void AVOpenGLWidget::SwapFrames()
@@ -121,7 +194,7 @@ bool AVOpenGLFrame::Update(AVFrame *frame, ChiakiLog *log)
{
auto f = QOpenGLContext::currentContext()->extraFunctions();
- if(frame->format != AV_PIX_FMT_YUV420P)
+ if(frame->format != conversion_config->pixel_format)
{
CHIAKI_LOGE(log, "AVOpenGLFrame got AVFrame with invalid format");
return false;
@@ -130,20 +203,16 @@ bool AVOpenGLFrame::Update(AVFrame *frame, ChiakiLog *log)
width = frame->width;
height = frame->height;
- for(int i=0; i<3; i++)
+ for(int i=0; iplanes; i++)
{
- int width = frame->width;
- int height = frame->height;
- if(i > 0)
- {
- width /= 2;
- height /= 2;
- }
+ int width = frame->width / conversion_config->plane_configs[i].width_divider;
+ int height = frame->height / conversion_config->plane_configs[i].height_divider;
+ int size = width * height * conversion_config->plane_configs[i].data_per_pixel;
f->glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[i]);
- f->glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height, nullptr, GL_STREAM_DRAW);
+ f->glBufferData(GL_PIXEL_UNPACK_BUFFER, size, nullptr, GL_STREAM_DRAW);
- auto buf = reinterpret_cast(f->glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, width * height, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
+ auto buf = reinterpret_cast(f->glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
if(!buf)
{
CHIAKI_LOGE(log, "AVOpenGLFrame failed to map PBO");
@@ -151,17 +220,17 @@ bool AVOpenGLFrame::Update(AVFrame *frame, ChiakiLog *log)
}
if(frame->linesize[i] == width)
- memcpy(buf, frame->data[i], width * height);
+ memcpy(buf, frame->data[i], size);
else
{
for(int l=0; ldata[i] + frame->linesize[i] * l, width);
+ memcpy(buf + width * l * conversion_config->plane_configs[i].data_per_pixel, frame->data[i] + frame->linesize[i] * l, width * conversion_config->plane_configs[i].data_per_pixel);
}
f->glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
f->glBindTexture(GL_TEXTURE_2D, tex[i]);
- f->glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+ f->glTexImage2D(GL_TEXTURE_2D, 0, conversion_config->plane_configs[i].internal_format, width, height, 0, conversion_config->plane_configs[i].format, GL_UNSIGNED_BYTE, nullptr);
}
f->glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
@@ -176,7 +245,7 @@ void AVOpenGLWidget::initializeGL()
auto f = QOpenGLContext::currentContext()->extraFunctions();
const char *gl_version = (const char *)f->glGetString(GL_VERSION);
- CHIAKI_LOGI(decoder->GetChiakiLog(), "OpenGL initialized with version \"%s\"", gl_version ? gl_version : "(null)");
+ CHIAKI_LOGI(session->GetChiakiLog(), "OpenGL initialized with version \"%s\"", gl_version ? gl_version : "(null)");
#ifdef DEBUG_OPENGL
auto logger = new QOpenGLDebugLogger(this);
@@ -197,16 +266,16 @@ void AVOpenGLWidget::initializeGL()
QVector info_log(info_log_size);
f->glGetShaderInfoLog(shader, info_log_size, &info_log_size, info_log.data());
f->glDeleteShader(shader);
- CHIAKI_LOGE(decoder->GetChiakiLog(), "Failed to Compile Shader:\n%s", info_log.data());
+ CHIAKI_LOGE(session->GetChiakiLog(), "Failed to Compile Shader:\n%s", info_log.data());
};
GLuint shader_vert = f->glCreateShader(GL_VERTEX_SHADER);
- f->glShaderSource(shader_vert, 1, &shader_vert_glsl, nullptr);
+ f->glShaderSource(shader_vert, 1, &conversion_config->shader_vert_glsl, nullptr);
f->glCompileShader(shader_vert);
CheckShaderCompiled(shader_vert);
GLuint shader_frag = f->glCreateShader(GL_FRAGMENT_SHADER);
- f->glShaderSource(shader_frag, 1, &shader_frag_glsl, nullptr);
+ f->glShaderSource(shader_frag, 1, &conversion_config->shader_frag_glsl, nullptr);
f->glCompileShader(shader_frag);
CheckShaderCompiled(shader_frag);
@@ -225,32 +294,37 @@ void AVOpenGLWidget::initializeGL()
QVector info_log(info_log_size);
f->glGetProgramInfoLog(program, info_log_size, &info_log_size, info_log.data());
f->glDeleteProgram(program);
- CHIAKI_LOGE(decoder->GetChiakiLog(), "Failed to Link Shader Program:\n%s", info_log.data());
+ CHIAKI_LOGE(session->GetChiakiLog(), "Failed to Link Shader Program:\n%s", info_log.data());
return;
}
for(int i=0; i<2; i++)
{
- f->glGenTextures(3, frames[i].tex);
- f->glGenBuffers(3, frames[i].pbo);
- uint8_t uv_default = 127;
- for(int j=0; j<3; j++)
+ frames[i].conversion_config = conversion_config;
+ f->glGenTextures(conversion_config->planes, frames[i].tex);
+ f->glGenBuffers(conversion_config->planes, frames[i].pbo);
+ uint8_t uv_default[] = {0x7f, 0x7f};
+ for(int j=0; jplanes; j++)
{
f->glBindTexture(GL_TEXTURE_2D, frames[i].tex[j]);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- f->glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, j > 0 ? &uv_default : nullptr);
+ f->glTexImage2D(GL_TEXTURE_2D, 0, conversion_config->plane_configs[j].internal_format, 1, 1, 0, conversion_config->plane_configs[j].format, GL_UNSIGNED_BYTE, j > 0 ? uv_default : nullptr);
}
frames[i].width = 0;
frames[i].height = 0;
}
f->glUseProgram(program);
- f->glUniform1i(f->glGetUniformLocation(program, "tex_y"), 0);
- f->glUniform1i(f->glGetUniformLocation(program, "tex_u"), 1);
- f->glUniform1i(f->glGetUniformLocation(program, "tex_v"), 2);
+
+ // bind only as many planes as we need
+ const char *plane_names[] = {"plane1", "plane2", "plane3"};
+ for(int i=0; iglUniform1i(f->glGetUniformLocation(program, plane_names[i]), i);
+ }
f->glGenVertexArrays(1, &vao);
f->glBindVertexArray(vao);
@@ -272,11 +346,14 @@ void AVOpenGLWidget::initializeGL()
frame_uploader_context->setShareContext(context());
if(!frame_uploader_context->create())
{
- CHIAKI_LOGE(decoder->GetChiakiLog(), "Failed to create upload OpenGL context");
+ CHIAKI_LOGE(session->GetChiakiLog(), "Failed to create upload OpenGL context");
return;
}
- frame_uploader = new AVOpenGLFrameUploader(decoder, this, frame_uploader_context, context()->surface());
+ frame_uploader_surface = new QOffscreenSurface();
+ frame_uploader_surface->setFormat(context()->format());
+ frame_uploader_surface->create();
+ frame_uploader = new AVOpenGLFrameUploader(session, this, frame_uploader_context, frame_uploader_surface);
frame_fg = 0;
frame_uploader_thread = new QThread(this);
@@ -304,10 +381,10 @@ void AVOpenGLWidget::paintGL()
vp_width = widget_width;
vp_height = widget_height;
}
- else
+ else if(transform_mode == TransformMode::Fit)
{
float aspect = (float)frame->width / (float)frame->height;
- if(aspect < (float)widget_width / (float)widget_height)
+ if(widget_height && aspect < (float)widget_width / (float)widget_height)
{
vp_height = widget_height;
vp_width = (GLsizei)(vp_height * aspect);
@@ -318,6 +395,34 @@ void AVOpenGLWidget::paintGL()
vp_height = (GLsizei)(vp_width / aspect);
}
}
+ else if(transform_mode == TransformMode::Zoom)
+ {
+ float aspect = (float)frame->width / (float)frame->height;
+ if(widget_height && aspect < (float)widget_width / (float)widget_height)
+ {
+ vp_width = widget_width;
+ vp_height = (GLsizei)(vp_width / aspect);
+ }
+ else
+ {
+ vp_height = widget_height;
+ vp_width = (GLsizei)(vp_height * aspect);
+ }
+ }
+ else // transform_mode == TransformMode::Stretch
+ {
+ float aspect = (float)frame->width / (float)frame->height;
+ if(widget_height && aspect < (float)widget_width / (float)widget_height)
+ {
+ vp_height = widget_height;
+ vp_width = widget_width;
+ }
+ else
+ {
+ vp_width = widget_width;
+ vp_height = widget_height;
+ }
+ }
f->glViewport((widget_width - vp_width) / 2, (widget_height - vp_height) / 2, vp_width, vp_height);
diff --git a/gui/src/controllermanager.cpp b/gui/src/controllermanager.cpp
index 77941de..8cc501f 100644
--- a/gui/src/controllermanager.cpp
+++ b/gui/src/controllermanager.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -26,6 +11,69 @@
#include
#endif
+static QSet chiaki_motion_controller_guids({
+ // Sony on Linux
+ "03000000341a00003608000011010000",
+ "030000004c0500006802000010010000",
+ "030000004c0500006802000010810000",
+ "030000004c0500006802000011010000",
+ "030000004c0500006802000011810000",
+ "030000006f0e00001402000011010000",
+ "030000008f0e00000300000010010000",
+ "050000004c0500006802000000010000",
+ "050000004c0500006802000000800000",
+ "050000004c0500006802000000810000",
+ "05000000504c415953544154494f4e00",
+ "060000004c0500006802000000010000",
+ "030000004c050000a00b000011010000",
+ "030000004c050000a00b000011810000",
+ "030000004c050000c405000011010000",
+ "030000004c050000c405000011810000",
+ "030000004c050000cc09000000010000",
+ "030000004c050000cc09000011010000",
+ "030000004c050000cc09000011810000",
+ "03000000c01100000140000011010000",
+ "050000004c050000c405000000010000",
+ "050000004c050000c405000000810000",
+ "050000004c050000c405000001800000",
+ "050000004c050000cc09000000010000",
+ "050000004c050000cc09000000810000",
+ "050000004c050000cc09000001800000",
+ // Sony on iOS
+ "050000004c050000cc090000df070000",
+ // Sony on Android
+ "050000004c05000068020000dfff3f00",
+ "030000004c050000cc09000000006800",
+ "050000004c050000c4050000fffe3f00",
+ "050000004c050000cc090000fffe3f00",
+ "050000004c050000cc090000ffff3f00",
+ "35643031303033326130316330353564",
+ // Sony on Mac OSx
+ "030000004c050000cc09000000000000",
+ "030000004c0500006802000000000000",
+ "030000004c0500006802000000010000",
+ "030000004c050000a00b000000010000",
+ "030000004c050000c405000000000000",
+ "030000004c050000c405000000010000",
+ "030000004c050000cc09000000010000",
+ "03000000c01100000140000000010000",
+ // Sony on Windows
+ "030000004c050000a00b000000000000",
+ "030000004c050000c405000000000000",
+ "030000004c050000cc09000000000000",
+ "03000000250900000500000000000000",
+ "030000004c0500006802000000000000",
+ "03000000632500007505000000000000",
+ "03000000888800000803000000000000",
+ "030000008f0e00001431000000000000",
+});
+
+static QSet> chiaki_dualsense_controller_ids({
+ // in format (vendor id, product id)
+ QPair(0x054c, 0x0ce6), // DualSense controller
+ QPair(0x054c, 0x0df2), // DualSense Edge controller
+});
+
static ControllerManager *instance = nullptr;
#define UPDATE_INTERVAL_MS 4
@@ -42,6 +90,15 @@ ControllerManager::ControllerManager(QObject *parent)
{
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
SDL_SetMainReady();
+#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE
+ SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
+#endif
+#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE
+ SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
+#endif
+#ifdef SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+#endif
if(SDL_Init(SDL_INIT_GAMECONTROLLER) < 0)
{
const char *err = SDL_GetError();
@@ -71,6 +128,24 @@ void ControllerManager::UpdateAvailableControllers()
{
if(!SDL_IsGameController(i))
continue;
+
+ // We'll try to identify pads with Motion Control
+ SDL_JoystickGUID guid = SDL_JoystickGetGUID(SDL_JoystickOpen(i));
+ char guid_str[256];
+ SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
+
+ if(chiaki_motion_controller_guids.contains(guid_str))
+ {
+ SDL_Joystick *joy = SDL_JoystickOpen(i);
+ if(joy)
+ {
+ bool no_buttons = SDL_JoystickNumButtons(joy) == 0;
+ SDL_JoystickClose(joy);
+ if(no_buttons)
+ continue;
+ }
+ }
+
current_controllers.insert(SDL_JoystickGetDeviceInstanceID(i));
}
@@ -96,27 +171,56 @@ void ControllerManager::HandleEvents()
break;
case SDL_CONTROLLERBUTTONUP:
case SDL_CONTROLLERBUTTONDOWN:
- ControllerEvent(event.cbutton.which);
- break;
case SDL_CONTROLLERAXISMOTION:
- ControllerEvent(event.caxis.which);
+#if not defined(CHIAKI_ENABLE_SETSU) and SDL_VERSION_ATLEAST(2, 0, 14)
+ case SDL_CONTROLLERSENSORUPDATE:
+ case SDL_CONTROLLERTOUCHPADDOWN:
+ case SDL_CONTROLLERTOUCHPADMOTION:
+ case SDL_CONTROLLERTOUCHPADUP:
+#endif
+ ControllerEvent(event);
break;
}
}
#endif
}
-void ControllerManager::ControllerEvent(int device_id)
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+void ControllerManager::ControllerEvent(SDL_Event event)
{
+ int device_id;
+ switch(event.type)
+ {
+ case SDL_CONTROLLERBUTTONDOWN:
+ case SDL_CONTROLLERBUTTONUP:
+ device_id = event.cbutton.which;
+ break;
+ case SDL_CONTROLLERAXISMOTION:
+ device_id = event.caxis.which;
+ break;
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ case SDL_CONTROLLERSENSORUPDATE:
+ device_id = event.csensor.which;
+ break;
+ case SDL_CONTROLLERTOUCHPADDOWN:
+ case SDL_CONTROLLERTOUCHPADMOTION:
+ case SDL_CONTROLLERTOUCHPADUP:
+ device_id = event.ctouchpad.which;
+ break;
+#endif
+ default:
+ return;
+ }
if(!open_controllers.contains(device_id))
return;
- open_controllers[device_id]->UpdateState();
+ open_controllers[device_id]->UpdateState(event);
}
+#endif
-QList ControllerManager::GetAvailableControllers()
+QSet ControllerManager::GetAvailableControllers()
{
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
- return available_controllers.toList();
+ return available_controllers;
#else
return {};
#endif
@@ -136,10 +240,13 @@ void ControllerManager::ControllerClosed(Controller *controller)
open_controllers.remove(controller->GetDeviceID());
}
-Controller::Controller(int device_id, ControllerManager *manager) : QObject(manager)
+Controller::Controller(int device_id, ControllerManager *manager)
+ : QObject(manager), is_dualsense(false)
{
this->id = device_id;
this->manager = manager;
+ chiaki_orientation_tracker_init(&this->orientation_tracker);
+ chiaki_controller_state_set_idle(&this->state);
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
controller = nullptr;
@@ -148,6 +255,14 @@ Controller::Controller(int device_id, ControllerManager *manager) : QObject(mana
if(SDL_JoystickGetDeviceInstanceID(i) == device_id)
{
controller = SDL_GameControllerOpen(i);
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ if(SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL))
+ SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
+ if(SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO))
+ SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
+#endif
+ auto controller_id = QPair(SDL_GameControllerGetVendor(controller), SDL_GameControllerGetProduct(controller));
+ is_dualsense = chiaki_dualsense_controller_ids.contains(controller_id);
break;
}
}
@@ -158,16 +273,197 @@ Controller::~Controller()
{
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
if(controller)
+ {
+ // Clear trigger effects, SDL doesn't do it automatically
+ const uint8_t clear_effect[10] = { 0 };
+ this->SetTriggerEffects(0x05, clear_effect, 0x05, clear_effect);
SDL_GameControllerClose(controller);
+ }
#endif
manager->ControllerClosed(this);
}
-void Controller::UpdateState()
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+void Controller::UpdateState(SDL_Event event)
{
+ switch(event.type)
+ {
+ case SDL_CONTROLLERBUTTONDOWN:
+ case SDL_CONTROLLERBUTTONUP:
+ if(!HandleButtonEvent(event.cbutton))
+ return;
+ break;
+ case SDL_CONTROLLERAXISMOTION:
+ if(!HandleAxisEvent(event.caxis))
+ return;
+ break;
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ case SDL_CONTROLLERSENSORUPDATE:
+ if(!HandleSensorEvent(event.csensor))
+ return;
+ break;
+ case SDL_CONTROLLERTOUCHPADDOWN:
+ case SDL_CONTROLLERTOUCHPADMOTION:
+ case SDL_CONTROLLERTOUCHPADUP:
+ if(!HandleTouchpadEvent(event.ctouchpad))
+ return;
+ break;
+#endif
+ default:
+ return;
+
+ }
emit StateChanged();
}
+inline bool Controller::HandleButtonEvent(SDL_ControllerButtonEvent event) {
+ ChiakiControllerButton ps_btn;
+ switch(event.button)
+ {
+ case SDL_CONTROLLER_BUTTON_A:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_CROSS;
+ break;
+ case SDL_CONTROLLER_BUTTON_B:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_MOON;
+ break;
+ case SDL_CONTROLLER_BUTTON_X:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_BOX;
+ break;
+ case SDL_CONTROLLER_BUTTON_Y:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_PYRAMID;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_UP:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_UP;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN;
+ break;
+ case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_L1;
+ break;
+ case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_R1;
+ break;
+ case SDL_CONTROLLER_BUTTON_LEFTSTICK:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_L3;
+ break;
+ case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_R3;
+ break;
+ case SDL_CONTROLLER_BUTTON_START:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_OPTIONS;
+ break;
+ case SDL_CONTROLLER_BUTTON_BACK:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_SHARE;
+ break;
+ case SDL_CONTROLLER_BUTTON_GUIDE:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_PS;
+ break;
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ case SDL_CONTROLLER_BUTTON_TOUCHPAD:
+ ps_btn = CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
+ break;
+#endif
+ default:
+ return false;
+ }
+ if(event.type == SDL_CONTROLLERBUTTONDOWN)
+ state.buttons |= ps_btn;
+ else
+ state.buttons &= ~ps_btn;
+ return true;
+}
+
+inline bool Controller::HandleAxisEvent(SDL_ControllerAxisEvent event) {
+ switch(event.axis)
+ {
+ case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
+ state.l2_state = (uint8_t)(event.value >> 7);
+ break;
+ case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
+ state.r2_state = (uint8_t)(event.value >> 7);
+ break;
+ case SDL_CONTROLLER_AXIS_LEFTX:
+ state.left_x = event.value;
+ break;
+ case SDL_CONTROLLER_AXIS_LEFTY:
+ state.left_y = event.value;
+ break;
+ case SDL_CONTROLLER_AXIS_RIGHTX:
+ state.right_x = event.value;
+ break;
+ case SDL_CONTROLLER_AXIS_RIGHTY:
+ state.right_y = event.value;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+inline bool Controller::HandleSensorEvent(SDL_ControllerSensorEvent event)
+{
+ switch(event.sensor)
+ {
+ case SDL_SENSOR_ACCEL:
+ state.accel_x = event.data[0] / SDL_STANDARD_GRAVITY;
+ state.accel_y = event.data[1] / SDL_STANDARD_GRAVITY;
+ state.accel_z = event.data[2] / SDL_STANDARD_GRAVITY;
+ break;
+ case SDL_SENSOR_GYRO:
+ state.gyro_x = event.data[0];
+ state.gyro_y = event.data[1];
+ state.gyro_z = event.data[2];
+ break;
+ default:
+ return false;
+ }
+ chiaki_orientation_tracker_update(
+ &orientation_tracker, state.gyro_x, state.gyro_y, state.gyro_z,
+ state.accel_x, state.accel_y, state.accel_z, event.timestamp * 1000);
+ chiaki_orientation_tracker_apply_to_controller_state(&orientation_tracker, &state);
+ return true;
+}
+
+inline bool Controller::HandleTouchpadEvent(SDL_ControllerTouchpadEvent event)
+{
+ auto key = qMakePair(event.touchpad, event.finger);
+ bool exists = touch_ids.contains(key);
+ uint8_t chiaki_id;
+ switch(event.type)
+ {
+ case SDL_CONTROLLERTOUCHPADDOWN:
+ if(touch_ids.size() >= CHIAKI_CONTROLLER_TOUCHES_MAX)
+ return false;
+ chiaki_id = chiaki_controller_state_start_touch(&state, event.x * PS_TOUCHPAD_MAX_X, event.y * PS_TOUCHPAD_MAX_Y);
+ touch_ids.insert(key, chiaki_id);
+ break;
+ case SDL_CONTROLLERTOUCHPADMOTION:
+ if(!exists)
+ return false;
+ chiaki_controller_state_set_touch_pos(&state, touch_ids[key], event.x * PS_TOUCHPAD_MAX_X, event.y * PS_TOUCHPAD_MAX_Y);
+ break;
+ case SDL_CONTROLLERTOUCHPADUP:
+ if(!exists)
+ return false;
+ chiaki_controller_state_stop_touch(&state, touch_ids[key]);
+ touch_ids.remove(key);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+#endif
+#endif
+
bool Controller::IsConnected()
{
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
@@ -191,7 +487,11 @@ QString Controller::GetName()
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
if(!controller)
return QString();
- return SDL_GameControllerName(controller);
+ SDL_Joystick *js = SDL_GameControllerGetJoystick(controller);
+ SDL_JoystickGUID guid = SDL_JoystickGetGUID(js);
+ char guid_str[256];
+ SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
+ return QString("%1 (%2)").arg(SDL_JoystickName(js), guid_str);
#else
return QString();
#endif
@@ -199,33 +499,39 @@ QString Controller::GetName()
ChiakiControllerState Controller::GetState()
{
- ChiakiControllerState state = {};
+ return state;
+}
+
+void Controller::SetRumble(uint8_t left, uint8_t right)
+{
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
if(!controller)
- return state;
-
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_A) ? CHIAKI_CONTROLLER_BUTTON_CROSS : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_B) ? CHIAKI_CONTROLLER_BUTTON_MOON : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_X) ? CHIAKI_CONTROLLER_BUTTON_BOX : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_Y) ? CHIAKI_CONTROLLER_BUTTON_PYRAMID : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) ? CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) ? CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP) ? CHIAKI_CONTROLLER_BUTTON_DPAD_UP : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) ? CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) ? CHIAKI_CONTROLLER_BUTTON_L1 : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) ? CHIAKI_CONTROLLER_BUTTON_R1 : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) ? CHIAKI_CONTROLLER_BUTTON_L3 : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) ? CHIAKI_CONTROLLER_BUTTON_R3 : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_START) ? CHIAKI_CONTROLLER_BUTTON_OPTIONS : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_BACK) ? CHIAKI_CONTROLLER_BUTTON_SHARE : 0;
- state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_GUIDE) ? CHIAKI_CONTROLLER_BUTTON_PS : 0;
- state.l2_state = (uint8_t)(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) >> 4);
- state.r2_state = (uint8_t)(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 4);
- state.left_x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
- state.left_y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
- state.right_x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
- state.right_y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
-
+ return;
+ SDL_GameControllerRumble(controller, (uint16_t)left << 8, (uint16_t)right << 8, 5000);
#endif
- return state;
-}
\ No newline at end of file
+}
+
+void Controller::SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right)
+{
+#if defined(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) && SDL_VERSION_ATLEAST(2, 0, 16)
+ if(!is_dualsense || !controller)
+ return;
+ DS5EffectsState_t state;
+ SDL_zero(state);
+ state.ucEnableBits1 |= (0x04 /* left trigger */ | 0x08 /* right trigger */);
+ state.rgucLeftTriggerEffect[0] = type_left;
+ SDL_memcpy(state.rgucLeftTriggerEffect + 1, data_left, 10);
+ state.rgucRightTriggerEffect[0] = type_right;
+ SDL_memcpy(state.rgucRightTriggerEffect + 1, data_right, 10);
+ SDL_GameControllerSendEffect(controller, &state, sizeof(state));
+#endif
+}
+
+bool Controller::IsDualSense()
+{
+#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ if(!controller)
+ return false;
+ return is_dualsense;
+#endif
+}
diff --git a/gui/src/discoverymanager.cpp b/gui/src/discoverymanager.cpp
index dc86395..ae1564f 100644
--- a/gui/src/discoverymanager.cpp
+++ b/gui/src/discoverymanager.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -73,7 +58,6 @@ void DiscoveryManager::SetActive(bool active)
sockaddr_in addr = {};
addr.sin_family = AF_INET;
- addr.sin_port = htons(CHIAKI_DISCOVERY_PORT);
addr.sin_addr.s_addr = 0xffffffff; // 255.255.255.255
options.send_addr = reinterpret_cast(&addr);
options.send_addr_size = sizeof(addr);
@@ -95,7 +79,7 @@ void DiscoveryManager::SetActive(bool active)
}
-void DiscoveryManager::SendWakeup(const QString &host, const QByteArray ®ist_key)
+void DiscoveryManager::SendWakeup(const QString &host, const QByteArray ®ist_key, bool ps5)
{
QByteArray key = regist_key;
for(size_t i=0; istate;
o.host_request_port = o.host_request_port;
#define CONVERT_STRING(name) if(h->name) { o.name = QString::fromLocal8Bit(h->name); }
diff --git a/gui/src/dynamicgridwidget.cpp b/gui/src/dynamicgridwidget.cpp
index cf330dd..c734f57 100644
--- a/gui/src/dynamicgridwidget.cpp
+++ b/gui/src/dynamicgridwidget.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
diff --git a/gui/src/host.cpp b/gui/src/host.cpp
index f81807c..9eab756 100644
--- a/gui/src/host.cpp
+++ b/gui/src/host.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -26,12 +11,13 @@ RegisteredHost::RegisteredHost()
}
RegisteredHost::RegisteredHost(const RegisteredHost &o)
- : ap_ssid(o.ap_ssid),
+ : target(o.target),
+ ap_ssid(o.ap_ssid),
ap_bssid(o.ap_bssid),
ap_key(o.ap_key),
ap_name(o.ap_name),
- ps4_mac(o.ps4_mac),
- ps4_nickname(o.ps4_nickname),
+ server_mac(o.server_mac),
+ server_nickname(o.server_nickname),
rp_key_type(o.rp_key_type)
{
memcpy(rp_regist_key, o.rp_regist_key, sizeof(rp_regist_key));
@@ -39,13 +25,14 @@ RegisteredHost::RegisteredHost(const RegisteredHost &o)
}
RegisteredHost::RegisteredHost(const ChiakiRegisteredHost &chiaki_host)
- : ps4_mac(chiaki_host.ps4_mac)
+ : server_mac(chiaki_host.server_mac)
{
+ target = chiaki_host.target;
ap_ssid = chiaki_host.ap_ssid;
ap_bssid = chiaki_host.ap_bssid;
ap_key = chiaki_host.ap_key;
ap_name = chiaki_host.ap_name;
- ps4_nickname = chiaki_host.ps4_nickname;
+ server_nickname = chiaki_host.server_nickname;
memcpy(rp_regist_key, chiaki_host.rp_regist_key, sizeof(rp_regist_key));
rp_key_type = chiaki_host.rp_key_type;
memcpy(rp_key, chiaki_host.rp_key, sizeof(rp_key));
@@ -53,12 +40,13 @@ RegisteredHost::RegisteredHost(const ChiakiRegisteredHost &chiaki_host)
void RegisteredHost::SaveToSettings(QSettings *settings) const
{
+ settings->setValue("target", (int)target);
settings->setValue("ap_ssid", ap_ssid);
settings->setValue("ap_bssid", ap_bssid);
settings->setValue("ap_key", ap_key);
settings->setValue("ap_name", ap_name);
- settings->setValue("ps4_nickname", ps4_nickname);
- settings->setValue("ps4_mac", QByteArray((const char *)ps4_mac.GetMAC(), 6));
+ settings->setValue("server_nickname", server_nickname);
+ settings->setValue("server_mac", QByteArray((const char *)server_mac.GetMAC(), 6));
settings->setValue("rp_regist_key", QByteArray(rp_regist_key, sizeof(rp_regist_key)));
settings->setValue("rp_key_type", rp_key_type);
settings->setValue("rp_key", QByteArray((const char *)rp_key, sizeof(rp_key)));
@@ -67,14 +55,15 @@ void RegisteredHost::SaveToSettings(QSettings *settings) const
RegisteredHost RegisteredHost::LoadFromSettings(QSettings *settings)
{
RegisteredHost r;
+ r.target = (ChiakiTarget)settings->value("target").toInt();
r.ap_ssid = settings->value("ap_ssid").toString();
r.ap_bssid = settings->value("ap_bssid").toString();
r.ap_key = settings->value("ap_key").toString();
r.ap_name = settings->value("ap_name").toString();
- r.ps4_nickname = settings->value("ps4_nickname").toString();
- auto ps4_mac = settings->value("ps4_mac").toByteArray();
- if(ps4_mac.size() == 6)
- r.ps4_mac = HostMAC((const uint8_t *)ps4_mac.constData());
+ r.server_nickname = settings->value("server_nickname").toString();
+ auto server_mac = settings->value("server_mac").toByteArray();
+ if(server_mac.size() == 6)
+ r.server_mac = HostMAC((const uint8_t *)server_mac.constData());
auto rp_regist_key = settings->value("rp_regist_key").toByteArray();
if(rp_regist_key.size() == sizeof(r.rp_regist_key))
memcpy(r.rp_regist_key, rp_regist_key.constData(), sizeof(r.rp_regist_key));
diff --git a/gui/src/loginpindialog.cpp b/gui/src/loginpindialog.cpp
index 51e0836..5d1b474 100644
--- a/gui/src/loginpindialog.cpp
+++ b/gui/src/loginpindialog.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
diff --git a/gui/src/main.cpp b/gui/src/main.cpp
index ec5ec30..5f2aad7 100644
--- a/gui/src/main.cpp
+++ b/gui/src/main.cpp
@@ -4,7 +4,6 @@ int real_main(int argc, char *argv[]);
int main(int argc, char *argv[]) { return real_main(argc, argv); }
#include
-#include
#include
#include
#include
@@ -40,7 +39,8 @@ struct CLICommand
};
static const QMap cli_commands = {
- { "discover", { chiaki_cli_cmd_discover } }
+ { "discover", { chiaki_cli_cmd_discover } },
+ { "wakeup", { chiaki_cli_cmd_wakeup } }
};
#endif
@@ -71,7 +71,11 @@ int real_main(int argc, char *argv[])
QApplication app(argc, argv);
+#ifdef Q_OS_MACOS
+ QApplication::setWindowIcon(QIcon(":/icons/chiaki_macos.svg"));
+#else
QApplication::setWindowIcon(QIcon(":/icons/chiaki.svg"));
+#endif
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
@@ -83,11 +87,14 @@ int real_main(int argc, char *argv[])
QStringList cmds;
cmds.append("stream");
+ cmds.append("list");
#ifdef CHIAKI_ENABLE_CLI
cmds.append(cli_commands.keys());
#endif
parser.addPositionalArgument("command", cmds.join(", "));
+ parser.addPositionalArgument("nickname", "Needed for stream command to get credentials for connecting. "
+ "Use 'list' to get the nickname.");
parser.addPositionalArgument("host", "Address to connect to (when using the stream command)");
QCommandLineOption regist_key_option("registkey", "", "registkey");
@@ -96,38 +103,97 @@ int real_main(int argc, char *argv[])
QCommandLineOption morning_option("morning", "", "morning");
parser.addOption(morning_option);
+ QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen mode [maintains aspect ratio, adds black bars to fill unsused parts of screen if applicable] (only for use with stream command)");
+ parser.addOption(fullscreen_option);
+
+ QCommandLineOption zoom_option("zoom", "Start window in fullscreen zoomed in to fit screen [maintains aspect ratio, cutting off edges of image to fill screen] (only for use with stream command)");
+ parser.addOption(zoom_option);
+
+ QCommandLineOption stretch_option("stretch", "Start window in fullscreen stretched to fit screen [distorts aspect ratio to fill screen] (only for use with stream command)");
+ parser.addOption(stretch_option);
+
parser.process(app);
QStringList args = parser.positionalArguments();
if(args.length() == 0)
return RunMain(app, &settings);
+ if(args[0] == "list")
+ {
+ for(const auto &host : settings.GetRegisteredHosts())
+ printf("Host: %s \n", host.GetServerNickname().toLocal8Bit().constData());
+ return 0;
+ }
if(args[0] == "stream")
{
if(args.length() < 2)
parser.showHelp(1);
- QString host = args[1];
+ //QString host = args[sizeof(args) -1]; //the ip is always the last param for stream
+ QString host = args[args.size()-1];
+ QByteArray morning;
+ QByteArray regist_key;
+ ChiakiTarget target = CHIAKI_TARGET_PS4_10;
- if(parser.value(regist_key_option).isEmpty() || parser.value(morning_option).isEmpty())
- parser.showHelp(1);
-
- QByteArray regist_key = parser.value(regist_key_option).toUtf8();
- if(regist_key.length() > sizeof(ChiakiConnectInfo::regist_key))
+ if(parser.value(regist_key_option).isEmpty() && parser.value(morning_option).isEmpty())
{
- printf("Given regist key is too long.\n");
- return 1;
+ if(args.length() < 3)
+ parser.showHelp(1);
+
+ bool found = false;
+ for(const auto &temphost : settings.GetRegisteredHosts())
+ {
+ if(temphost.GetServerNickname() == args[1])
+ {
+ found = true;
+ morning = temphost.GetRPKey();
+ regist_key = temphost.GetRPRegistKey();
+ target = temphost.GetTarget();
+ break;
+ }
+ }
+ if(!found)
+ {
+ printf("No configuration found for '%s'\n", args[1].toLocal8Bit().constData());
+ return 1;
+ }
}
- regist_key += QByteArray(sizeof(ChiakiConnectInfo::regist_key) - regist_key.length(), 0);
-
- QByteArray morning = QByteArray::fromBase64(parser.value(morning_option).toUtf8());
- if(morning.length() != sizeof(ChiakiConnectInfo::morning))
+ else
{
- printf("Given morning has invalid size (expected %llu)", (unsigned long long)sizeof(ChiakiConnectInfo::morning));
+ // TODO: explicit option for target
+ regist_key = parser.value(regist_key_option).toUtf8();
+ if(regist_key.length() > sizeof(ChiakiConnectInfo::regist_key))
+ {
+ printf("Given regist key is too long (expected size <=%llu, got %d)\n",
+ (unsigned long long)sizeof(ChiakiConnectInfo::regist_key),
+ regist_key.length());
+ return 1;
+ }
+ regist_key += QByteArray(sizeof(ChiakiConnectInfo::regist_key) - regist_key.length(), 0);
+ morning = QByteArray::fromBase64(parser.value(morning_option).toUtf8());
+ if(morning.length() != sizeof(ChiakiConnectInfo::morning))
+ {
+ printf("Given morning has invalid size (expected %llu, got %d)\n",
+ (unsigned long long)sizeof(ChiakiConnectInfo::morning),
+ morning.length());
+ printf("Given morning has invalid size (expected %llu)", (unsigned long long)sizeof(ChiakiConnectInfo::morning));
+ return 1;
+ }
+ }
+ if ((parser.isSet(stretch_option) && (parser.isSet(zoom_option) || parser.isSet(fullscreen_option))) || (parser.isSet(zoom_option) && parser.isSet(fullscreen_option)))
+ {
+ printf("Must choose between fullscreen, zoom or stretch option.");
return 1;
}
- StreamSessionConnectInfo connect_info(&settings, host, regist_key, morning);
+ StreamSessionConnectInfo connect_info(
+ &settings,
+ target,
+ host,
+ regist_key,
+ morning,
+ parser.isSet(fullscreen_option),
+ parser.isSet(zoom_option) ? TransformMode::Zoom : parser.isSet(stretch_option) ? TransformMode::Stretch : TransformMode::Fit);
return RunStream(app, connect_info);
}
@@ -163,11 +229,9 @@ int RunMain(QApplication &app, Settings *settings)
return app.exec();
}
-
-
int RunStream(QApplication &app, const StreamSessionConnectInfo &connect_info)
{
- StreamWindow window(connect_info);
+ StreamWindow *window = new StreamWindow(connect_info);
app.setQuitOnLastWindowClosed(true);
return app.exec();
}
diff --git a/gui/src/mainwindow.cpp b/gui/src/mainwindow.cpp
index c5fcc48..0912b2e 100644
--- a/gui/src/mainwindow.cpp
+++ b/gui/src/mainwindow.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -161,6 +146,11 @@ MainWindow::MainWindow(Settings *settings, QWidget *parent)
AddToolBarAction(settings_action);
connect(settings_action, &QAction::triggered, this, &MainWindow::ShowSettings);
+ auto quit_action = new QAction(tr("Quit"), this);
+ quit_action->setShortcut(Qt::CTRL + Qt::Key_Q);
+ addAction(quit_action);
+ connect(quit_action, &QAction::triggered, this, &MainWindow::Quit);
+
auto scroll_area = new QScrollArea(this);
scroll_area->setWidgetResizable(true);
scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -182,7 +172,7 @@ MainWindow::MainWindow(Settings *settings, QWidget *parent)
grid_widget->setContentsMargins(0, 0, 0, 0);
resize(800, 600);
-
+
connect(&discovery_manager, &DiscoveryManager::HostsUpdated, this, &MainWindow::UpdateDisplayServers);
connect(settings, &Settings::RegisteredHostsUpdated, this, &MainWindow::UpdateDisplayServers);
connect(settings, &Settings::ManualHostsUpdated, this, &MainWindow::UpdateDisplayServers);
@@ -227,7 +217,8 @@ void MainWindow::SendWakeup(const DisplayServer *server)
try
{
- discovery_manager.SendWakeup(server->GetHostAddr(), server->registered_host.GetRPRegistKey());
+ discovery_manager.SendWakeup(server->GetHostAddr(), server->registered_host.GetRPRegistKey(),
+ chiaki_target_is_ps5(server->registered_host.GetTarget()));
}
catch(const Exception &e)
{
@@ -263,7 +254,14 @@ void MainWindow::ServerItemWidgetTriggered()
}
QString host = server.GetHostAddr();
- StreamSessionConnectInfo info(settings, host, server.registered_host.GetRPRegistKey(), server.registered_host.GetRPKey());
+ StreamSessionConnectInfo info(
+ settings,
+ server.registered_host.GetTarget(),
+ host,
+ server.registered_host.GetRPRegistKey(),
+ server.registered_host.GetRPKey(),
+ false,
+ TransformMode::Fit);
new StreamWindow(info);
}
else
@@ -312,6 +310,11 @@ void MainWindow::ShowSettings()
dialog.exec();
}
+void MainWindow::Quit()
+{
+ qApp->exit();
+}
+
void MainWindow::UpdateDisplayServers()
{
display_servers.clear();
@@ -344,7 +347,7 @@ void MainWindow::UpdateDisplayServers()
display_servers.append(server);
}
-
+
UpdateServerWidgets();
}
diff --git a/gui/src/manualhostdialog.cpp b/gui/src/manualhostdialog.cpp
index 9547eba..d184d3a 100644
--- a/gui/src/manualhostdialog.cpp
+++ b/gui/src/manualhostdialog.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -53,7 +38,7 @@ ManualHostDialog::ManualHostDialog(Settings *settings, int id, QWidget *parent)
registered_host_combo_box->addItem(tr("Register on first Connection"));
auto registered_hosts = settings->GetRegisteredHosts();
for(const auto ®istered_host : registered_hosts)
- registered_host_combo_box->addItem(QString("%1 (%2)").arg(registered_host.GetPS4MAC().ToString(), registered_host.GetPS4Nickname()), QVariant::fromValue(registered_host.GetPS4MAC()));
+ registered_host_combo_box->addItem(QString("%1 (%2)").arg(registered_host.GetServerMAC().ToString(), registered_host.GetServerNickname()), QVariant::fromValue(registered_host.GetServerMAC()));
form_layout->addRow(tr("Registered Console:"), registered_host_combo_box);
button_box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Discard, this);
@@ -89,4 +74,4 @@ void ManualHostDialog::accept()
ManualHost host(host_id, host_edit->text().trimmed(), registered, registered_mac);
settings->SetManualHost(host);
QDialog::accept();
-}
\ No newline at end of file
+}
diff --git a/gui/src/registdialog.cpp b/gui/src/registdialog.cpp
index 07e0844..4829da1 100644
--- a/gui/src/registdialog.cpp
+++ b/gui/src/registdialog.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -59,20 +44,32 @@ RegistDialog::RegistDialog(Settings *settings, const QString &host, QWidget *par
broadcast_check_box->setChecked(host.isEmpty());
auto UpdatePSNIDEdits = [this]() {
- psn_online_id_edit->setEnabled(psn_online_id_radio_button->isChecked());
- psn_account_id_edit->setEnabled(psn_account_id_radio_button->isChecked());
+ bool need_account_id = NeedAccountId();
+ psn_online_id_edit->setEnabled(!need_account_id);
+ psn_account_id_edit->setEnabled(need_account_id);
};
- psn_online_id_radio_button = new QRadioButton(tr("PSN Online-ID (username, case-sensitive) for PS4 < 7.0:"), this);
- psn_online_id_edit = new QLineEdit(this);
- form_layout->addRow(psn_online_id_radio_button, psn_online_id_edit);
- connect(psn_online_id_radio_button, &QRadioButton::toggled, this, UpdatePSNIDEdits);
+ auto target_layout = new QVBoxLayout(nullptr);
+ ps4_pre9_radio_button = new QRadioButton(tr("PS4 Firmware < 7.0"), this);
+ target_layout->addWidget(ps4_pre9_radio_button);
+ connect(ps4_pre9_radio_button, &QRadioButton::toggled, this, UpdatePSNIDEdits);
+ ps4_pre10_radio_button = new QRadioButton(tr("PS4 Firmware >= 7.0, < 8.0"), this);
+ target_layout->addWidget(ps4_pre10_radio_button);
+ connect(ps4_pre10_radio_button, &QRadioButton::toggled, this, UpdatePSNIDEdits);
+ ps4_10_radio_button = new QRadioButton(tr("PS4 Firmware >= 8.0"), this);
+ target_layout->addWidget(ps4_10_radio_button);
+ connect(ps4_10_radio_button, &QRadioButton::toggled, this, UpdatePSNIDEdits);
+ ps5_radio_button = new QRadioButton(tr("PS5"), this);
+ target_layout->addWidget(ps5_radio_button);
+ connect(ps5_radio_button, &QRadioButton::toggled, this, UpdatePSNIDEdits);
+ form_layout->addRow(tr("Console:"), target_layout);
- psn_account_id_radio_button = new QRadioButton(tr("PSN Account-ID (base64) for PS4 >= 7.0:"), this);
+ psn_online_id_edit = new QLineEdit(this);
+ form_layout->addRow(tr("PSN Online-ID (username, case-sensitive):"), psn_online_id_edit);
psn_account_id_edit = new QLineEdit(this);
- form_layout->addRow(psn_account_id_radio_button, psn_account_id_edit);
- psn_account_id_radio_button->setChecked(true);
- connect(psn_account_id_radio_button, &QRadioButton::toggled, this, UpdatePSNIDEdits);
+ form_layout->addRow(tr("PSN Account-ID (base64):"), psn_account_id_edit);
+
+ ps5_radio_button->setChecked(true);
UpdatePSNIDEdits();
@@ -96,11 +93,17 @@ RegistDialog::~RegistDialog()
{
}
+bool RegistDialog::NeedAccountId()
+{
+ return !ps4_pre9_radio_button->isChecked();
+}
+
void RegistDialog::ValidateInput()
{
+ bool need_account_id = NeedAccountId();
bool valid = !host_edit->text().trimmed().isEmpty()
- && (!psn_online_id_radio_button->isChecked() || !psn_online_id_edit->text().trimmed().isEmpty())
- && (!psn_account_id_radio_button->isChecked() || !psn_account_id_radio_button->text().trimmed().isEmpty())
+ && !(!need_account_id && psn_online_id_edit->text().trimmed().isEmpty())
+ && !(need_account_id && psn_account_id_edit->text().trimmed().isEmpty())
&& pin_edit->text().length() == PIN_LENGTH;
register_button->setEnabled(valid);
}
@@ -111,8 +114,18 @@ void RegistDialog::accept()
QByteArray host = host_edit->text().trimmed().toUtf8();
info.host = host.constData();
+ if(ps4_pre9_radio_button->isChecked())
+ info.target = CHIAKI_TARGET_PS4_8;
+ else if(ps4_pre10_radio_button->isChecked())
+ info.target = CHIAKI_TARGET_PS4_9;
+ else if(ps4_10_radio_button->isChecked())
+ info.target = CHIAKI_TARGET_PS4_10;
+ else
+ info.target = CHIAKI_TARGET_PS5_1;
+
+ bool need_account_id = NeedAccountId();
QByteArray psn_id; // keep this out of the if scope
- if(psn_online_id_radio_button->isChecked())
+ if(!need_account_id)
{
psn_id = psn_online_id_edit->text().trimmed().toUtf8();
info.psn_online_id = psn_id.constData();
@@ -192,14 +205,14 @@ void RegistExecuteDialog::Finished()
void RegistExecuteDialog::Success(RegisteredHost host)
{
- CHIAKI_LOGI(&log, "Successfully registered %s", host.GetPS4Nickname().toLocal8Bit().constData());
+ CHIAKI_LOGI(&log, "Successfully registered %s", host.GetServerNickname().toLocal8Bit().constData());
this->registered_host = host;
- if(settings->GetRegisteredHostRegistered(host.GetPS4MAC()))
+ if(settings->GetRegisteredHostRegistered(host.GetServerMAC()))
{
int r = QMessageBox::question(this,
tr("Console already registered"),
- tr("The console with ID %1 has already been registered. Should the previous record be overwritten?").arg(host.GetPS4MAC().ToString()));
+ tr("The console with ID %1 has already been registered. Should the previous record be overwritten?").arg(host.GetServerMAC().ToString()));
if(r == QMessageBox::No)
{
accept();
@@ -209,7 +222,7 @@ void RegistExecuteDialog::Success(RegisteredHost host)
settings->AddRegisteredHost(host);
- QMessageBox::information(this, tr("Console registered"), tr("The Console %1 with ID %2 has been successfully registered!").arg(host.GetPS4Nickname(), host.GetPS4MAC().ToString()));
+ QMessageBox::information(this, tr("Console registered"), tr("The Console %1 with ID %2 has been successfully registered!").arg(host.GetServerNickname(), host.GetServerMAC().ToString()));
accept();
}
diff --git a/gui/src/servericonwidget.cpp b/gui/src/servericonwidget.cpp
index e9d4e08..6eb3d99 100644
--- a/gui/src/servericonwidget.cpp
+++ b/gui/src/servericonwidget.cpp
@@ -1,93 +1,94 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
+#include
ServerIconWidget::ServerIconWidget(QWidget *parent) : QWidget(parent)
{
+ LoadSvg();
}
void ServerIconWidget::paintEvent(QPaintEvent *event)
{
- static const float icon_aspect = 100.0f / 17.0f;
- static const float coolness = 0.585913713f;
-
- if(width() == 0 || height() == 0)
+ QRectF view_box = svg_renderer.viewBoxF();
+ if(view_box.width() < 0.00001f || view_box.height() < 0.00001f)
return;
+ float icon_aspect = view_box.width() / view_box.height();
+ float widget_aspect = (float)width() / (float)height();
+ QRectF icon_rect;
+ if(widget_aspect > icon_aspect)
+ {
+ icon_rect.setHeight(height());
+ icon_rect.setWidth((float)height() * icon_aspect);
+ icon_rect.moveTop(0.0f);
+ icon_rect.moveLeft(((float)width() - icon_rect.width()) * 0.5f);
+ }
+ else
+ {
+ icon_rect.setWidth(width());
+ icon_rect.setHeight((float)width() / icon_aspect);
+ icon_rect.moveLeft(0.0f);
+ icon_rect.moveTop(((float)height() - icon_rect.height()) * 0.5f);
+ }
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
- QRectF icon_rect;
- float widget_aspect = (float)width() / (float)height();
- if(widget_aspect > icon_aspect)
- {
- icon_rect.setHeight(height());
- icon_rect.setWidth((float)height() * icon_aspect);
- icon_rect.moveTop(0.0f);
- icon_rect.moveLeft(((float)width() - icon_rect.width()) * 0.5f);
- }
- else
- {
- icon_rect.setWidth(width());
- icon_rect.setHeight((float)width() / icon_aspect);
- icon_rect.moveLeft(0.0f);
- icon_rect.moveTop(((float)height() - icon_rect.height()) * 0.5f);
- }
-
-
- auto XForY = [&icon_rect](float y, bool right)
- {
- float r = (icon_rect.height() - y) * coolness;
- if(right)
- r += icon_rect.width() - icon_rect.height() * coolness;
- return r;
+ auto render_element = [&view_box, &icon_rect, this](QPainter &painter, const QString &id) {
+ QRectF src = /*svg_renderer.transformForElement(id).mapRect(*/svg_renderer.boundsOnElement(id)/*)*/;
+ QRectF dst = src.translated(-view_box.left(), -view_box.top());
+ dst = QRectF(
+ icon_rect.width() * dst.left() / view_box.width(),
+ icon_rect.height() * dst.top() / view_box.height(),
+ icon_rect.width() * dst.width() / view_box.width(),
+ icon_rect.height() * dst.height() / view_box.height());
+ dst = dst.translated(icon_rect.left(), icon_rect.top());
+ svg_renderer.render(&painter, id, dst);
};
- auto SectionPath = [&XForY, &icon_rect](float y0, float y1)
- {
- QPainterPath path;
- path.moveTo(XForY(y0, false), y0);
- path.lineTo(XForY(y1, false), y1);
- path.lineTo(XForY(y1, true), y1);
- path.lineTo(XForY(y0, true), y0);
- path.translate(icon_rect.topLeft());
- return path;
- };
-
- auto color = palette().color(QPalette::Text);
-
- QColor bar_color;
switch(state)
{
- case CHIAKI_DISCOVERY_HOST_STATE_STANDBY:
- bar_color = QColor(255, 174, 47);
- break;
case CHIAKI_DISCOVERY_HOST_STATE_READY:
- bar_color = QColor(0, 167, 255);
+ render_element(painter, "light_on");
+ break;
+ case CHIAKI_DISCOVERY_HOST_STATE_STANDBY:
+ render_element(painter, "light_standby");
break;
default:
break;
}
- if(bar_color.isValid())
- painter.fillPath(SectionPath(icon_rect.height() * 0.41f - 1.0f, icon_rect.height() * 0.59f + 1.0f), bar_color);
- painter.fillPath(SectionPath(0, icon_rect.height() * 0.41f), color);
- painter.fillPath(SectionPath(icon_rect.height() * 0.59f, icon_rect.height()), color);
-}
\ No newline at end of file
+ auto color = palette().color(QPalette::Text);
+
+ QImage temp_image((int)(devicePixelRatioF() * width()),
+ (int)(devicePixelRatioF() * height()),
+ QImage::Format_ARGB32_Premultiplied);
+ {
+ temp_image.setDevicePixelRatio(devicePixelRatioF());
+ temp_image.fill(QColor(0, 0, 0, 0));
+ QPainter temp_painter(&temp_image);
+ render_element(temp_painter, "console");
+ temp_painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
+ temp_painter.fillRect(icon_rect, color);
+ }
+ painter.drawImage(QRectF(0.0f, 0.0f, width(), height()), temp_image);
+}
+
+void ServerIconWidget::LoadSvg()
+{
+ QString path = ps5 ? ":/icons/console-ps5.svg" : ":/icons/console-ps4.svg";
+ svg_renderer.load(path);
+ update();
+}
+
+void ServerIconWidget::SetState(bool ps5, ChiakiDiscoveryHostState state)
+{
+ bool reload = this->ps5 != ps5;
+ this->ps5 = ps5;
+ this->state = state;
+ if(reload)
+ LoadSvg();
+ update();
+}
diff --git a/gui/src/serveritemwidget.cpp b/gui/src/serveritemwidget.cpp
index 1771478..13e1937 100644
--- a/gui/src/serveritemwidget.cpp
+++ b/gui/src/serveritemwidget.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -85,13 +70,14 @@ void ServerItemWidget::Update(const DisplayServer &display_server)
delete_action->setEnabled(!display_server.discovered);
wake_action->setEnabled(display_server.registered);
- icon_widget->SetState(display_server.discovered ? display_server.discovery_host.state : CHIAKI_DISCOVERY_HOST_STATE_UNKNOWN);
+ icon_widget->SetState(display_server.IsPS5(),
+ display_server.discovered ? display_server.discovery_host.state : CHIAKI_DISCOVERY_HOST_STATE_UNKNOWN);
QString top_text = "";
if(display_server.discovered || display_server.registered)
{
- top_text += (display_server.discovered ? display_server.discovery_host.host_name : display_server.registered_host.GetPS4Nickname()) + "\n";
+ top_text += (display_server.discovered ? display_server.discovery_host.host_name : display_server.registered_host.GetServerNickname()) + "\n";
}
top_text += tr("Address: %1").arg(display_server.GetHostAddr());
@@ -99,7 +85,7 @@ void ServerItemWidget::Update(const DisplayServer &display_server)
if(display_server.discovered || display_server.registered)
{
top_text += "\n" + tr("ID: %1 (%2)").arg(
- display_server.discovered ? display_server.discovery_host.GetHostMAC().ToString() : display_server.registered_host.GetPS4MAC().ToString(),
+ display_server.discovered ? display_server.discovery_host.GetHostMAC().ToString() : display_server.registered_host.GetServerMAC().ToString(),
display_server.registered ? tr("registered") : tr("unregistered"));
}
diff --git a/gui/src/sessionlog.cpp b/gui/src/sessionlog.cpp
index 77664d1..a64c484 100644
--- a/gui/src/sessionlog.cpp
+++ b/gui/src/sessionlog.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
diff --git a/gui/src/settings.cpp b/gui/src/settings.cpp
index b74552b..6d296d1 100644
--- a/gui/src/settings.cpp
+++ b/gui/src/settings.cpp
@@ -1,26 +1,78 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
+#include
-#define SETTINGS_VERSION 1
+#include
+
+#define SETTINGS_VERSION 2
+
+static void MigrateSettingsTo2(QSettings *settings)
+{
+ QList> hosts;
+ int count = settings->beginReadArray("registered_hosts");
+ for(int i=0; isetArrayIndex(i);
+ QMap host;
+ for(QString k : settings->allKeys())
+ host[k] = settings->value(k);
+ hosts.append(host);
+ }
+ settings->endArray();
+ settings->remove("registered_hosts");
+ settings->beginWriteArray("registered_hosts");
+ int i=0;
+ for(const auto &host : hosts)
+ {
+ settings->setArrayIndex(i);
+ settings->setValue("target", (int)CHIAKI_TARGET_PS4_10);
+ for(auto it = host.constBegin(); it != host.constEnd(); it++)
+ {
+ QString k = it.key();
+ if(k == "ps4_nickname")
+ k = "server_nickname";
+ else if(k == "ps4_mac")
+ k = "server_mac";
+ settings->setValue(k, it.value());
+ }
+ i++;
+ }
+ settings->endArray();
+ QString hw_decoder = settings->value("settings/hw_decode_engine").toString();
+ settings->remove("settings/hw_decode_engine");
+ if(hw_decoder != "none")
+ settings->setValue("settings/hw_decoder", hw_decoder);
+}
+
+static void MigrateSettings(QSettings *settings)
+{
+ int version_prev = settings->value("version", 0).toInt();
+ if(version_prev < 1)
+ return;
+ if(version_prev > SETTINGS_VERSION)
+ {
+ CHIAKI_LOGE(NULL, "Settings version %d is higher than application one (%d)", version_prev, SETTINGS_VERSION);
+ return;
+ }
+ while(version_prev < SETTINGS_VERSION)
+ {
+ version_prev++;
+ switch(version_prev)
+ {
+ case 2:
+ CHIAKI_LOGI(NULL, "Migrating settings to 2");
+ MigrateSettingsTo2(settings);
+ break;
+ default:
+ break;
+ }
+ }
+}
Settings::Settings(QObject *parent) : QObject(parent)
{
+ MigrateSettings(&settings);
manual_hosts_id_next = 0;
settings.setValue("version", SETTINGS_VERSION);
LoadRegisteredHosts();
@@ -36,10 +88,10 @@ uint32_t Settings::GetLogLevelMask()
}
static const QMap resolutions = {
- { CHIAKI_VIDEO_RESOLUTION_PRESET_360p, "360p"},
- { CHIAKI_VIDEO_RESOLUTION_PRESET_540p, "540p"},
- { CHIAKI_VIDEO_RESOLUTION_PRESET_720p, "720p"},
- { CHIAKI_VIDEO_RESOLUTION_PRESET_1080p, "1080p"}
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_360p, "360p" },
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_540p, "540p" },
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_720p, "720p" },
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_1080p, "1080p" }
};
static const ChiakiVideoResolutionPreset resolution_default = CHIAKI_VIDEO_RESOLUTION_PRESET_720p;
@@ -83,6 +135,24 @@ void Settings::SetBitrate(unsigned int bitrate)
settings.setValue("settings/bitrate", bitrate);
}
+static const QMap codecs = {
+ { CHIAKI_CODEC_H264, "h264" },
+ { CHIAKI_CODEC_H265, "h265" }
+};
+
+static const ChiakiCodec codec_default = CHIAKI_CODEC_H265;
+
+ChiakiCodec Settings::GetCodec() const
+{
+ auto v = settings.value("settings/codec", codecs[codec_default]).toString();
+ return codecs.key(v, codec_default);
+}
+
+void Settings::SetCodec(ChiakiCodec codec)
+{
+ settings.setValue("settings/codec", codecs[codec]);
+}
+
unsigned int Settings::GetAudioBufferSizeDefault() const
{
return 9600;
@@ -93,12 +163,54 @@ unsigned int Settings::GetAudioBufferSizeRaw() const
return settings.value("settings/audio_buffer_size", 0).toUInt();
}
+static const QMap decoder_values = {
+ { Decoder::Ffmpeg, "ffmpeg" },
+ { Decoder::Pi, "pi" }
+};
+
+static const Decoder decoder_default = Decoder::Pi;
+
+Decoder Settings::GetDecoder() const
+{
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ auto v = settings.value("settings/decoder", decoder_values[decoder_default]).toString();
+ return decoder_values.key(v, decoder_default);
+#else
+ return Decoder::Ffmpeg;
+#endif
+}
+
+void Settings::SetDecoder(Decoder decoder)
+{
+ settings.setValue("settings/decoder", decoder_values[decoder]);
+}
+
+QString Settings::GetHardwareDecoder() const
+{
+ return settings.value("settings/hw_decoder").toString();
+}
+
+void Settings::SetHardwareDecoder(const QString &hw_decoder)
+{
+ settings.setValue("settings/hw_decoder", hw_decoder);
+}
+
unsigned int Settings::GetAudioBufferSize() const
{
unsigned int v = GetAudioBufferSizeRaw();
return v ? v : GetAudioBufferSizeDefault();
}
+QString Settings::GetAudioOutDevice() const
+{
+ return settings.value("settings/audio_out_device").toString();
+}
+
+void Settings::SetAudioOutDevice(QString device_name)
+{
+ settings.setValue("settings/audio_out_device", device_name);
+}
+
void Settings::SetAudioBufferSize(unsigned int size)
{
settings.setValue("settings/audio_buffer_size", size);
@@ -106,14 +218,34 @@ void Settings::SetAudioBufferSize(unsigned int size)
ChiakiConnectVideoProfile Settings::GetVideoProfile()
{
- ChiakiConnectVideoProfile profile;
+ ChiakiConnectVideoProfile profile = {};
chiaki_connect_video_profile_preset(&profile, GetResolution(), GetFPS());
unsigned int bitrate = GetBitrate();
if(bitrate)
profile.bitrate = bitrate;
+ profile.codec = GetCodec();
return profile;
}
+static const QMap disconnect_action_values = {
+ { DisconnectAction::Ask, "ask" },
+ { DisconnectAction::AlwaysNothing, "nothing" },
+ { DisconnectAction::AlwaysSleep, "sleep" }
+};
+
+static const DisconnectAction disconnect_action_default = DisconnectAction::Ask;
+
+DisconnectAction Settings::GetDisconnectAction()
+{
+ auto v = settings.value("settings/disconnect_action", disconnect_action_values[disconnect_action_default]).toString();
+ return disconnect_action_values.key(v, disconnect_action_default);
+}
+
+void Settings::SetDisconnectAction(DisconnectAction action)
+{
+ settings.setValue("settings/disconnect_action", disconnect_action_values[action]);
+}
+
void Settings::LoadRegisteredHosts()
{
registered_hosts.clear();
@@ -123,7 +255,7 @@ void Settings::LoadRegisteredHosts()
{
settings.setArrayIndex(i);
RegisteredHost host = RegisteredHost::LoadFromSettings(&settings);
- registered_hosts[host.GetPS4MAC()] = host;
+ registered_hosts[host.GetServerMAC()] = host;
}
settings.endArray();
}
@@ -143,7 +275,7 @@ void Settings::SaveRegisteredHosts()
void Settings::AddRegisteredHost(const RegisteredHost &host)
{
- registered_hosts[host.GetPS4MAC()] = host;
+ registered_hosts[host.GetServerMAC()] = host;
SaveRegisteredHosts();
emit RegisteredHostsUpdated();
}
@@ -169,7 +301,7 @@ void Settings::LoadManualHosts()
if(host.GetID() < 0)
continue;
if(manual_hosts_id_next <= host.GetID())
- manual_hosts_id_next = host.GetID();
+ manual_hosts_id_next = host.GetID() + 1;
manual_hosts[host.GetID()] = host;
}
settings.endArray();
@@ -207,3 +339,99 @@ void Settings::RemoveManualHost(int id)
SaveManualHosts();
emit ManualHostsUpdated();
}
+
+QString Settings::GetChiakiControllerButtonName(int button)
+{
+ switch(button)
+ {
+ case CHIAKI_CONTROLLER_BUTTON_CROSS : return tr("Cross");
+ case CHIAKI_CONTROLLER_BUTTON_MOON : return tr("Moon");
+ case CHIAKI_CONTROLLER_BUTTON_BOX : return tr("Box");
+ case CHIAKI_CONTROLLER_BUTTON_PYRAMID : return tr("Pyramid");
+ case CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT : return tr("D-Pad Left");
+ case CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT : return tr("D-Pad Right");
+ case CHIAKI_CONTROLLER_BUTTON_DPAD_UP : return tr("D-Pad Up");
+ case CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN : return tr("D-Pad Down");
+ case CHIAKI_CONTROLLER_BUTTON_L1 : return tr("L1");
+ case CHIAKI_CONTROLLER_BUTTON_R1 : return tr("R1");
+ case CHIAKI_CONTROLLER_BUTTON_L3 : return tr("L3");
+ case CHIAKI_CONTROLLER_BUTTON_R3 : return tr("R3");
+ case CHIAKI_CONTROLLER_BUTTON_OPTIONS : return tr("Options");
+ case CHIAKI_CONTROLLER_BUTTON_SHARE : return tr("Share");
+ case CHIAKI_CONTROLLER_BUTTON_TOUCHPAD : return tr("Touchpad");
+ case CHIAKI_CONTROLLER_BUTTON_PS : return tr("PS");
+ case CHIAKI_CONTROLLER_ANALOG_BUTTON_L2 : return tr("L2");
+ case CHIAKI_CONTROLLER_ANALOG_BUTTON_R2 : return tr("R2");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_X_UP) : return tr("Left Stick Right");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_Y_UP) : return tr("Left Stick Up");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_X_UP) : return tr("Right Stick Right");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_Y_UP) : return tr("Right Stick Up");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_X_DOWN) : return tr("Left Stick Left");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_Y_DOWN) : return tr("Left Stick Down");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_X_DOWN) : return tr("Right Stick Left");
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_Y_DOWN) : return tr("Right Stick Down");
+ default: return "Unknown";
+ }
+}
+
+void Settings::SetControllerButtonMapping(int chiaki_button, Qt::Key key)
+{
+ auto button_name = GetChiakiControllerButtonName(chiaki_button).replace(' ', '_').toLower();
+ settings.setValue("keymap/" + button_name, QKeySequence(key).toString());
+}
+
+QMap Settings::GetControllerMapping()
+{
+ // Initialize with default values
+ QMap result =
+ {
+ {CHIAKI_CONTROLLER_BUTTON_CROSS , Qt::Key::Key_Return},
+ {CHIAKI_CONTROLLER_BUTTON_MOON , Qt::Key::Key_Backspace},
+ {CHIAKI_CONTROLLER_BUTTON_BOX , Qt::Key::Key_Backslash},
+ {CHIAKI_CONTROLLER_BUTTON_PYRAMID , Qt::Key::Key_C},
+ {CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT , Qt::Key::Key_Left},
+ {CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT, Qt::Key::Key_Right},
+ {CHIAKI_CONTROLLER_BUTTON_DPAD_UP , Qt::Key::Key_Up},
+ {CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN , Qt::Key::Key_Down},
+ {CHIAKI_CONTROLLER_BUTTON_L1 , Qt::Key::Key_2},
+ {CHIAKI_CONTROLLER_BUTTON_R1 , Qt::Key::Key_3},
+ {CHIAKI_CONTROLLER_BUTTON_L3 , Qt::Key::Key_5},
+ {CHIAKI_CONTROLLER_BUTTON_R3 , Qt::Key::Key_6},
+ {CHIAKI_CONTROLLER_BUTTON_OPTIONS , Qt::Key::Key_O},
+ {CHIAKI_CONTROLLER_BUTTON_SHARE , Qt::Key::Key_F},
+ {CHIAKI_CONTROLLER_BUTTON_TOUCHPAD , Qt::Key::Key_T},
+ {CHIAKI_CONTROLLER_BUTTON_PS , Qt::Key::Key_Escape},
+ {CHIAKI_CONTROLLER_ANALOG_BUTTON_L2 , Qt::Key::Key_1},
+ {CHIAKI_CONTROLLER_ANALOG_BUTTON_R2 , Qt::Key::Key_4},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_X_UP) , Qt::Key::Key_BracketRight},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_X_DOWN) , Qt::Key::Key_BracketLeft},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_Y_UP) , Qt::Key::Key_Insert},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_Y_DOWN) , Qt::Key::Key_Delete},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_X_UP) , Qt::Key::Key_Equal},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_X_DOWN), Qt::Key::Key_Minus},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_Y_UP) , Qt::Key::Key_PageUp},
+ {static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_Y_DOWN), Qt::Key::Key_PageDown}
+ };
+
+ // Then fill in from settings
+ auto chiaki_buttons = result.keys();
+ for(auto chiaki_button : chiaki_buttons)
+ {
+ auto button_name = GetChiakiControllerButtonName(chiaki_button).replace(' ', '_').toLower();
+ if(settings.contains("keymap/" + button_name))
+ result[static_cast(chiaki_button)] = Qt::Key(QKeySequence(settings.value("keymap/" + button_name).toString())[0]);
+ }
+
+ return result;
+}
+
+QMap Settings::GetControllerMappingForDecoding()
+{
+ auto map = GetControllerMapping();
+ QMap result;
+ for(auto it = map.begin(); it != map.end(); ++it)
+ {
+ result[it.value()] = it.key();
+ }
+ return result;
+}
diff --git a/gui/src/settingsdialog.cpp b/gui/src/settingsdialog.cpp
index 86b47d9..1770932 100644
--- a/gui/src/settingsdialog.cpp
+++ b/gui/src/settingsdialog.cpp
@@ -1,25 +1,12 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -31,36 +18,46 @@
#include
#include
#include
+#include
+#include
+#include
+#include
const char * const about_string =
"
Chiaki
by thestr4ng3r, version " CHIAKI_VERSION
""
"
This program is free software: you can redistribute it and/or modify "
- "it under the terms of the GNU General Public License as published by "
- "the Free Software Foundation, either version 3 of the License, or "
- "(at your option) any later version.
"
+ "it under the terms of the GNU Affero General Public License version 3 "
+ "as published by the Free Software Foundation.
"
""
"
This program is distributed in the hope that it will be useful, "
"but WITHOUT ANY WARRANTY; without even the implied warranty of "
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
"GNU General Public License for more details.
";
-
SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(parent)
{
this->settings = settings;
setWindowTitle(tr("Settings"));
- auto layout = new QVBoxLayout(this);
- setLayout(layout);
+ auto root_layout = new QVBoxLayout(this);
+ setLayout(root_layout);
+ auto horizontal_layout = new QHBoxLayout();
+ root_layout->addLayout(horizontal_layout);
+
+ auto left_layout = new QVBoxLayout();
+ horizontal_layout->addLayout(left_layout);
+
+ auto right_layout = new QVBoxLayout();
+ horizontal_layout->addLayout(right_layout);
// General
auto general_group_box = new QGroupBox(tr("General"));
- layout->addWidget(general_group_box);
+ left_layout->addWidget(general_group_box);
auto general_layout = new QFormLayout();
general_group_box->setLayout(general_layout);
@@ -72,10 +69,62 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa
log_verbose_check_box->setChecked(settings->GetLogVerbose());
connect(log_verbose_check_box, &QCheckBox::stateChanged, this, &SettingsDialog::LogVerboseChanged);
+ dualsense_check_box = new QCheckBox(this);
+ general_layout->addRow(tr("Extended DualSense Support:\nEnable haptics and adaptive triggers\nfor attached DualSense controllers.\nThis is currently experimental."), dualsense_check_box);
+ dualsense_check_box->setChecked(settings->GetDualSenseEnabled());
+ connect(dualsense_check_box, &QCheckBox::stateChanged, this, &SettingsDialog::DualSenseChanged);
+
auto log_directory_label = new QLineEdit(GetLogBaseDir(), this);
log_directory_label->setReadOnly(true);
general_layout->addRow(tr("Log Directory:"), log_directory_label);
+ disconnect_action_combo_box = new QComboBox(this);
+ QList> disconnect_action_strings = {
+ { DisconnectAction::AlwaysNothing, "Do Nothing"},
+ { DisconnectAction::AlwaysSleep, "Enter Sleep Mode"},
+ { DisconnectAction::Ask, "Ask"}
+ };
+ auto current_disconnect_action = settings->GetDisconnectAction();
+ for(const auto &p : disconnect_action_strings)
+ {
+ disconnect_action_combo_box->addItem(tr(p.second), (int)p.first);
+ if(current_disconnect_action == p.first)
+ disconnect_action_combo_box->setCurrentIndex(disconnect_action_combo_box->count() - 1);
+ }
+ connect(disconnect_action_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(DisconnectActionSelected()));
+
+ general_layout->addRow(tr("Action on Disconnect:"), disconnect_action_combo_box);
+
+ audio_device_combo_box = new QComboBox(this);
+ audio_device_combo_box->addItem(tr("Auto"));
+ auto current_audio_device = settings->GetAudioOutDevice();
+ if(!current_audio_device.isEmpty())
+ {
+ // temporarily add the selected device before async fetching is done
+ audio_device_combo_box->addItem(current_audio_device, current_audio_device);
+ audio_device_combo_box->setCurrentIndex(1);
+ }
+ connect(audio_device_combo_box, QOverload::of(&QComboBox::activated), this, [this](){
+ this->settings->SetAudioOutDevice(audio_device_combo_box->currentData().toString());
+ });
+
+ // do this async because it's slow, assuming availableDevices() is thread-safe
+ auto audio_devices_future = QtConcurrent::run([]() {
+ return QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
+ });
+ auto audio_devices_future_watcher = new QFutureWatcher>(this);
+ connect(audio_devices_future_watcher, &QFutureWatcher>::finished, this, [this, audio_devices_future_watcher, settings]() {
+ auto available_devices = audio_devices_future_watcher->result();
+ while(audio_device_combo_box->count() > 1) // remove all but "Auto"
+ audio_device_combo_box->removeItem(1);
+ for(QAudioDeviceInfo di : available_devices)
+ audio_device_combo_box->addItem(di.deviceName(), di.deviceName());
+ int audio_out_device_index = audio_device_combo_box->findData(settings->GetAudioOutDevice());
+ audio_device_combo_box->setCurrentIndex(audio_out_device_index < 0 ? 0 : audio_out_device_index);
+ });
+ audio_devices_future_watcher->setFuture(audio_devices_future);
+ general_layout->addRow(tr("Audio Output Device:"), audio_device_combo_box);
+
auto about_button = new QPushButton(tr("About Chiaki"), this);
general_layout->addRow(about_button);
connect(about_button, &QPushButton::clicked, this, [this]() {
@@ -85,17 +134,17 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa
// Stream Settings
auto stream_settings_group_box = new QGroupBox(tr("Stream Settings"));
- layout->addWidget(stream_settings_group_box);
+ left_layout->addWidget(stream_settings_group_box);
auto stream_settings_layout = new QFormLayout();
stream_settings_group_box->setLayout(stream_settings_layout);
resolution_combo_box = new QComboBox(this);
static const QList> resolution_strings = {
- { CHIAKI_VIDEO_RESOLUTION_PRESET_360p, "360p"},
- { CHIAKI_VIDEO_RESOLUTION_PRESET_540p, "540p"},
- { CHIAKI_VIDEO_RESOLUTION_PRESET_720p, "720p"},
- { CHIAKI_VIDEO_RESOLUTION_PRESET_1080p, "1080p (PS4 Pro only)"}
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_360p, "360p" },
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_540p, "540p" },
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_720p, "720p" },
+ { CHIAKI_VIDEO_RESOLUTION_PRESET_1080p, "1080p (PS5 and PS4 Pro only)" }
};
auto current_res = settings->GetResolution();
for(const auto &p : resolution_strings)
@@ -130,18 +179,71 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa
connect(bitrate_edit, &QLineEdit::textEdited, this, &SettingsDialog::BitrateEdited);
UpdateBitratePlaceholder();
+ codec_combo_box = new QComboBox(this);
+ static const QList> codec_strings = {
+ { CHIAKI_CODEC_H264, "H264" },
+ { CHIAKI_CODEC_H265, "H265 (PS5 only)" }
+ };
+ auto current_codec = settings->GetCodec();
+ for(const auto &p : codec_strings)
+ {
+ codec_combo_box->addItem(p.second, (int)p.first);
+ if(current_codec == p.first)
+ codec_combo_box->setCurrentIndex(codec_combo_box->count() - 1);
+ }
+ connect(codec_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(CodecSelected()));
+ stream_settings_layout->addRow(tr("Codec:"), codec_combo_box);
+
audio_buffer_size_edit = new QLineEdit(this);
- audio_buffer_size_edit->setValidator(new QIntValidator(1024, 0x20000));
+ audio_buffer_size_edit->setValidator(new QIntValidator(1024, 0x20000, audio_buffer_size_edit));
unsigned int audio_buffer_size = settings->GetAudioBufferSizeRaw();
audio_buffer_size_edit->setText(audio_buffer_size ? QString::number(audio_buffer_size) : "");
stream_settings_layout->addRow(tr("Audio Buffer Size:"), audio_buffer_size_edit);
audio_buffer_size_edit->setPlaceholderText(tr("Default (%1)").arg(settings->GetAudioBufferSizeDefault()));
connect(audio_buffer_size_edit, &QLineEdit::textEdited, this, &SettingsDialog::AudioBufferSizeEdited);
+ // Decode Settings
+
+ auto decode_settings = new QGroupBox(tr("Decode Settings"));
+ left_layout->addWidget(decode_settings);
+
+ auto decode_settings_layout = new QFormLayout();
+ decode_settings->setLayout(decode_settings_layout);
+
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ pi_decoder_check_box = new QCheckBox(this);
+ pi_decoder_check_box->setChecked(settings->GetDecoder() == Decoder::Pi);
+ connect(pi_decoder_check_box, &QCheckBox::toggled, this, [this](bool checked) {
+ this->settings->SetDecoder(checked ? Decoder::Pi : Decoder::Ffmpeg);
+ UpdateHardwareDecodeEngineComboBox();
+ });
+ decode_settings_layout->addRow(tr("Use Raspberry Pi Decoder:"), pi_decoder_check_box);
+#else
+ pi_decoder_check_box = nullptr;
+#endif
+
+ hw_decoder_combo_box = new QComboBox(this);
+ hw_decoder_combo_box->addItem("none", QString());
+ auto current_hw_decoder = settings->GetHardwareDecoder();
+ enum AVHWDeviceType hw_dev = AV_HWDEVICE_TYPE_NONE;
+ while(true)
+ {
+ hw_dev = av_hwdevice_iterate_types(hw_dev);
+ if(hw_dev == AV_HWDEVICE_TYPE_NONE)
+ break;
+ QString name = QString::fromUtf8(av_hwdevice_get_type_name(hw_dev));
+ hw_decoder_combo_box->addItem(name, name);
+ if(current_hw_decoder == name)
+ hw_decoder_combo_box->setCurrentIndex(hw_decoder_combo_box->count() - 1);
+ }
+ connect(hw_decoder_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(HardwareDecodeEngineSelected()));
+ decode_settings_layout->addRow(tr("Hardware decode method:"), hw_decoder_combo_box);
+ UpdateHardwareDecodeEngineComboBox();
+
// Registered Consoles
auto registered_hosts_group_box = new QGroupBox(tr("Registered Consoles"));
- layout->addWidget(registered_hosts_group_box);
+ left_layout->addWidget(registered_hosts_group_box);
auto registered_hosts_layout = new QHBoxLayout();
registered_hosts_group_box->setLayout(registered_hosts_layout);
@@ -162,8 +264,43 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa
registered_hosts_buttons_layout->addStretch();
+ // Key Settings
+ auto key_settings_group_box = new QGroupBox(tr("Key Settings"));
+ right_layout->addWidget(key_settings_group_box);
+ auto key_horizontal = new QHBoxLayout();
+ key_settings_group_box->setLayout(key_horizontal);
+ key_horizontal->setSpacing(10);
+
+ auto key_left_form = new QFormLayout();
+ auto key_right_form = new QFormLayout();
+ key_horizontal->addLayout(key_left_form);
+ key_horizontal->addLayout(key_right_form);
+
+ QMap key_map = this->settings->GetControllerMapping();
+
+ int i = 0;
+ for(auto it = key_map.begin(); it != key_map.end(); ++it, ++i)
+ {
+ int chiaki_button = it.key();
+ auto button = new QPushButton(QKeySequence(it.value()).toString(), this);
+ button->setAutoDefault(false);
+ auto form = i % 2 ? key_left_form : key_right_form;
+ form->addRow(Settings::GetChiakiControllerButtonName(chiaki_button), button);
+ // Launch key capture dialog on clicked event
+ connect(button, &QPushButton::clicked, this, [this, chiaki_button, button](){
+ auto dialog = new SettingsKeyCaptureDialog(this);
+ // Store captured key to settings and change button label on KeyCaptured event
+ connect(dialog, &SettingsKeyCaptureDialog::KeyCaptured, button, [this, button, chiaki_button](Qt::Key key){
+ button->setText(QKeySequence(key).toString());
+ this->settings->SetControllerButtonMapping(chiaki_button, key);
+ });
+ dialog->exec();
+ });
+ }
+
+ // Close Button
auto button_box = new QDialogButtonBox(QDialogButtonBox::Close, this);
- layout->addWidget(button_box);
+ root_layout->addWidget(button_box);
connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
button_box->button(QDialogButtonBox::Close)->setDefault(true);
@@ -180,11 +317,21 @@ void SettingsDialog::ResolutionSelected()
UpdateBitratePlaceholder();
}
+void SettingsDialog::DisconnectActionSelected()
+{
+ settings->SetDisconnectAction(static_cast(disconnect_action_combo_box->currentData().toInt()));
+}
+
void SettingsDialog::LogVerboseChanged()
{
settings->SetLogVerbose(log_verbose_check_box->isChecked());
}
+void SettingsDialog::DualSenseChanged()
+{
+ settings->SetDualSenseEnabled(dualsense_check_box->isChecked());
+}
+
void SettingsDialog::FPSSelected()
{
settings->SetFPS((ChiakiVideoFPSPreset)fps_combo_box->currentData().toInt());
@@ -195,11 +342,31 @@ void SettingsDialog::BitrateEdited()
settings->SetBitrate(bitrate_edit->text().toUInt());
}
+void SettingsDialog::CodecSelected()
+{
+ settings->SetCodec((ChiakiCodec)codec_combo_box->currentData().toInt());
+}
+
void SettingsDialog::AudioBufferSizeEdited()
{
settings->SetAudioBufferSize(audio_buffer_size_edit->text().toUInt());
}
+void SettingsDialog::AudioOutputSelected()
+{
+ settings->SetAudioOutDevice(audio_device_combo_box->currentText());
+}
+
+void SettingsDialog::HardwareDecodeEngineSelected()
+{
+ settings->SetHardwareDecoder(hw_decoder_combo_box->currentData().toString());
+}
+
+void SettingsDialog::UpdateHardwareDecodeEngineComboBox()
+{
+ hw_decoder_combo_box->setEnabled(settings->GetDecoder() == Decoder::Ffmpeg);
+}
+
void SettingsDialog::UpdateBitratePlaceholder()
{
bitrate_edit->setPlaceholderText(tr("Automatic (%1)").arg(settings->GetVideoProfile().bitrate));
@@ -211,8 +378,11 @@ void SettingsDialog::UpdateRegisteredHosts()
auto hosts = settings->GetRegisteredHosts();
for(const auto &host : hosts)
{
- auto item = new QListWidgetItem(QString("%1 (%2)").arg(host.GetPS4MAC().ToString(), host.GetPS4Nickname()));
- item->setData(Qt::UserRole, QVariant::fromValue(host.GetPS4MAC()));
+ auto item = new QListWidgetItem(QString("%1 (%2, %3)")
+ .arg(host.GetServerMAC().ToString(),
+ chiaki_target_is_ps5(host.GetTarget()) ? "PS5" : "PS4",
+ host.GetServerNickname()));
+ item->setData(Qt::UserRole, QVariant::fromValue(host.GetServerMAC()));
registered_hosts_list_widget->addItem(item);
}
}
@@ -241,4 +411,4 @@ void SettingsDialog::DeleteRegisteredHost()
return;
settings->RemoveRegisteredHost(mac);
-}
\ No newline at end of file
+}
diff --git a/gui/src/settingskeycapturedialog.cpp b/gui/src/settingskeycapturedialog.cpp
new file mode 100644
index 0000000..a631444
--- /dev/null
+++ b/gui/src/settingskeycapturedialog.cpp
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include "settingskeycapturedialog.h"
+
+#include
+#include
+#include
+#include
+
+SettingsKeyCaptureDialog::SettingsKeyCaptureDialog(QWidget *parent)
+{
+ setWindowTitle(tr("Key Capture"));
+
+ auto root_layout = new QVBoxLayout(this);
+ setLayout(root_layout);
+
+ auto label = new QLabel(tr("Press any key to configure button or click close."));
+ root_layout->addWidget(label);
+
+ auto button = new QPushButton(tr("Close"), this);
+ root_layout->addWidget(button);
+ button->setAutoDefault(false);
+ connect(button, &QPushButton::clicked, this, &QDialog::accept);
+}
+
+void SettingsKeyCaptureDialog::keyReleaseEvent(QKeyEvent *event)
+{
+ KeyCaptured(Qt::Key(event->key()));
+ accept();
+}
diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp
index 8635b07..42c9959 100644
--- a/gui/src/streamsession.cpp
+++ b/gui/src/streamsession.cpp
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -21,59 +6,135 @@
#include
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
-#include
-#include
-#endif
-
#include
#include
#include
#include
-StreamSessionConnectInfo::StreamSessionConnectInfo()
-{
- log_level_mask = CHIAKI_LOG_ALL;
- std::memset(&video_profile, 0, sizeof(video_profile));
- audio_buffer_size = 9600;
-}
+#define SETSU_UPDATE_INTERVAL_MS 4
-StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, QString host, QByteArray regist_key, QByteArray morning)
+#ifdef Q_OS_LINUX
+#define DUALSENSE_AUDIO_DEVICE_NEEDLE "DualSense"
+#else
+#define DUALSENSE_AUDIO_DEVICE_NEEDLE "Wireless Controller"
+#endif
+
+StreamSessionConnectInfo::StreamSessionConnectInfo(
+ Settings *settings,
+ ChiakiTarget target,
+ QString host,
+ QByteArray regist_key,
+ QByteArray morning,
+ bool fullscreen,
+ TransformMode transform_mode)
+ : settings(settings)
{
+ key_map = settings->GetControllerMappingForDecoding();
+ decoder = settings->GetDecoder();
+ hw_decoder = settings->GetHardwareDecoder();
+ audio_out_device = settings->GetAudioOutDevice();
log_level_mask = settings->GetLogLevelMask();
log_file = CreateLogFilename();
video_profile = settings->GetVideoProfile();
+ this->target = target;
this->host = host;
this->regist_key = regist_key;
this->morning = morning;
audio_buffer_size = settings->GetAudioBufferSize();
+ this->fullscreen = fullscreen;
+ this->transform_mode = transform_mode;
+ this->enable_keyboard = false; // TODO: from settings
+ this->enable_dualsense = settings->GetDualSenseEnabled();
}
static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user);
static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user);
-static bool VideoSampleCb(uint8_t *buf, size_t buf_size, void *user);
+static void HapticsFrameCb(uint8_t *buf, size_t buf_size, void *user);
static void EventCb(ChiakiEvent *event, void *user);
+#if CHIAKI_GUI_ENABLE_SETSU
+static void SessionSetsuCb(SetsuEvent *event, void *user);
+#endif
+static void FfmpegFrameCb(ChiakiFfmpegDecoder *decoder, void *user);
StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent)
: QObject(parent),
log(this, connect_info.log_level_mask, connect_info.log_file),
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- gamepad(nullptr),
+ ffmpeg_decoder(nullptr),
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ pi_decoder(nullptr),
#endif
- controller(nullptr),
- video_decoder(log.GetChiakiLog()),
audio_output(nullptr),
- audio_io(nullptr)
+ audio_io(nullptr),
+ haptics_output(0),
+ haptics_resampler_buf(nullptr)
{
+ connected = false;
+ ChiakiErrorCode err;
+
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ if(connect_info.decoder == Decoder::Pi)
+ {
+ pi_decoder = CHIAKI_NEW(ChiakiPiDecoder);
+ if(chiaki_pi_decoder_init(pi_decoder, log.GetChiakiLog()) != CHIAKI_ERR_SUCCESS)
+ throw ChiakiException("Failed to initialize Raspberry Pi Decoder");
+ }
+ else
+ {
+#endif
+ ffmpeg_decoder = new ChiakiFfmpegDecoder;
+ ChiakiLogSniffer sniffer;
+ chiaki_log_sniffer_init(&sniffer, CHIAKI_LOG_ALL, GetChiakiLog());
+ err = chiaki_ffmpeg_decoder_init(ffmpeg_decoder,
+ chiaki_log_sniffer_get_log(&sniffer),
+ chiaki_target_is_ps5(connect_info.target) ? connect_info.video_profile.codec : CHIAKI_CODEC_H264,
+ connect_info.hw_decoder.isEmpty() ? NULL : connect_info.hw_decoder.toUtf8().constData(),
+ FfmpegFrameCb, this);
+ if(err != CHIAKI_ERR_SUCCESS)
+ {
+ QString log = QString::fromUtf8(chiaki_log_sniffer_get_buffer(&sniffer));
+ chiaki_log_sniffer_fini(&sniffer);
+ throw ChiakiException("Failed to initialize FFMPEG Decoder:\n" + log);
+ }
+ chiaki_log_sniffer_fini(&sniffer);
+ ffmpeg_decoder->log = GetChiakiLog();
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ }
+#endif
+
+ audio_out_device_info = QAudioDeviceInfo::defaultOutputDevice();
+ if(!connect_info.audio_out_device.isEmpty())
+ {
+ for(QAudioDeviceInfo di : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
+ {
+ if(di.deviceName() == connect_info.audio_out_device)
+ {
+ audio_out_device_info = di;
+ break;
+ }
+ }
+ }
+
chiaki_opus_decoder_init(&opus_decoder, log.GetChiakiLog());
audio_buffer_size = connect_info.audio_buffer_size;
QByteArray host_str = connect_info.host.toUtf8();
- ChiakiConnectInfo chiaki_connect_info;
+ ChiakiConnectInfo chiaki_connect_info = {};
+ chiaki_connect_info.ps5 = chiaki_target_is_ps5(connect_info.target);
chiaki_connect_info.host = host_str.constData();
chiaki_connect_info.video_profile = connect_info.video_profile;
+ chiaki_connect_info.video_profile_auto_downgrade = true;
+ chiaki_connect_info.enable_keyboard = false;
+ chiaki_connect_info.enable_dualsense = connect_info.enable_dualsense;
+
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ if(connect_info.decoder == Decoder::Pi && chiaki_connect_info.video_profile.codec != CHIAKI_CODEC_H264)
+ {
+ CHIAKI_LOGW(GetChiakiLog(), "A codec other than H264 was requested for Pi Decoder. Falling back to it.");
+ chiaki_connect_info.video_profile.codec = CHIAKI_CODEC_H264;
+ }
+#endif
if(connect_info.regist_key.size() != sizeof(chiaki_connect_info.regist_key))
throw ChiakiException("RegistKey invalid");
@@ -83,9 +144,9 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
throw ChiakiException("Morning invalid");
memcpy(chiaki_connect_info.morning, connect_info.morning.constData(), sizeof(chiaki_connect_info.morning));
- memset(&keyboard_state, 0, sizeof(keyboard_state));
+ chiaki_controller_state_set_idle(&keyboard_state);
- ChiakiErrorCode err = chiaki_session_init(&session, &chiaki_connect_info, log.GetChiakiLog());
+ err = chiaki_session_init(&session, &chiaki_connect_info, GetChiakiLog());
if(err != CHIAKI_ERR_SUCCESS)
throw ChiakiException("Chiaki Session Init failed: " + QString::fromLocal8Bit(chiaki_error_string(err)));
@@ -94,16 +155,55 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
chiaki_opus_decoder_get_sink(&opus_decoder, &audio_sink);
chiaki_session_set_audio_sink(&session, &audio_sink);
- chiaki_session_set_video_sample_cb(&session, VideoSampleCb, this);
+ if(connect_info.enable_dualsense)
+ {
+ ChiakiAudioSink haptics_sink;
+ haptics_sink.user = this;
+ haptics_sink.frame_cb = HapticsFrameCb;
+ chiaki_session_set_haptics_sink(&session, &haptics_sink);
+ }
+
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ if(pi_decoder)
+ chiaki_session_set_video_sample_cb(&session, chiaki_pi_decoder_video_sample_cb, pi_decoder);
+ else
+ {
+#endif
+ chiaki_session_set_video_sample_cb(&session, chiaki_ffmpeg_decoder_video_sample_cb, ffmpeg_decoder);
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ }
+#endif
+
chiaki_session_set_event_cb(&session, EventCb, this);
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- connect(QGamepadManager::instance(), &QGamepadManager::connectedGamepadsChanged, this, &StreamSession::UpdateGamepads);
-#endif
#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
connect(ControllerManager::GetInstance(), &ControllerManager::AvailableControllersUpdated, this, &StreamSession::UpdateGamepads);
#endif
+#if CHIAKI_GUI_ENABLE_SETSU
+ setsu_motion_device = nullptr;
+ chiaki_controller_state_set_idle(&setsu_state);
+ orient_dirty = true;
+ chiaki_orientation_tracker_init(&orient_tracker);
+ setsu = setsu_new();
+ auto timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this, [this]{
+ setsu_poll(setsu, SessionSetsuCb, this);
+ if(orient_dirty)
+ {
+ chiaki_orientation_tracker_apply_to_controller_state(&orient_tracker, &setsu_state);
+ SendFeedbackState();
+ orient_dirty = false;
+ }
+ });
+ timer->start(SETSU_UPDATE_INTERVAL_MS);
+#endif
+
+ key_map = connect_info.key_map;
+ if(connect_info.enable_dualsense)
+ {
+ InitHaptics();
+ }
UpdateGamepads();
}
@@ -112,12 +212,35 @@ StreamSession::~StreamSession()
chiaki_session_join(&session);
chiaki_session_fini(&session);
chiaki_opus_decoder_fini(&opus_decoder);
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- delete gamepad;
-#endif
#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
- delete controller;
+ for(auto controller : controllers)
+ delete controller;
#endif
+#if CHIAKI_GUI_ENABLE_SETSU
+ setsu_free(setsu);
+#endif
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ if(pi_decoder)
+ {
+ chiaki_pi_decoder_fini(pi_decoder);
+ free(pi_decoder);
+ }
+#endif
+ if(ffmpeg_decoder)
+ {
+ chiaki_ffmpeg_decoder_fini(ffmpeg_decoder);
+ delete ffmpeg_decoder;
+ }
+ if(haptics_output > 0)
+ {
+ SDL_CloseAudioDevice(haptics_output);
+ haptics_output = 0;
+ }
+ if(haptics_resampler_buf)
+ {
+ free(haptics_resampler_buf);
+ haptics_resampler_buf = nullptr;
+ }
}
void StreamSession::Start()
@@ -135,116 +258,118 @@ void StreamSession::Stop()
chiaki_session_stop(&session);
}
+void StreamSession::GoToBed()
+{
+ chiaki_session_goto_bed(&session);
+}
+
void StreamSession::SetLoginPIN(const QString &pin)
{
QByteArray data = pin.toUtf8();
chiaki_session_set_login_pin(&session, (const uint8_t *)data.constData(), data.size());
}
+bool StreamSession::HandleMouseEvent(QMouseEvent *event)
+{
+ if(event->button() != Qt::MouseButton::LeftButton)
+ return false;
+ if(event->type() == QEvent::MouseButtonPress)
+ keyboard_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
+ else
+ keyboard_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
+ SendFeedbackState();
+ return true;
+}
+
void StreamSession::HandleKeyboardEvent(QKeyEvent *event)
{
- uint64_t button_mask;
- switch(event->key())
+ if(key_map.contains(Qt::Key(event->key())) == false)
+ return;
+
+ if(event->isAutoRepeat())
+ return;
+
+ int button = key_map[Qt::Key(event->key())];
+ bool press_event = event->type() == QEvent::Type::KeyPress;
+
+ switch(button)
{
- case Qt::Key::Key_Left:
- button_mask = CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT;
+ case CHIAKI_CONTROLLER_ANALOG_BUTTON_L2:
+ keyboard_state.l2_state = press_event ? 0xff : 0;
break;
- case Qt::Key::Key_Right:
- button_mask = CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT;
+ case CHIAKI_CONTROLLER_ANALOG_BUTTON_R2:
+ keyboard_state.r2_state = press_event ? 0xff : 0;
break;
- case Qt::Key::Key_Up:
- button_mask = CHIAKI_CONTROLLER_BUTTON_DPAD_UP;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_Y_UP):
+ keyboard_state.right_y = press_event ? -0x7fff : 0;
break;
- case Qt::Key::Key_Down:
- button_mask = CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_Y_DOWN):
+ keyboard_state.right_y = press_event ? 0x7fff : 0;
break;
- case Qt::Key::Key_Return:
- button_mask = CHIAKI_CONTROLLER_BUTTON_CROSS;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_X_UP):
+ keyboard_state.right_x = press_event ? 0x7fff : 0;
break;
- case Qt::Key::Key_Backspace:
- button_mask = CHIAKI_CONTROLLER_BUTTON_MOON;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_RIGHT_X_DOWN):
+ keyboard_state.right_x = press_event ? -0x7fff : 0;
break;
- case Qt::Key::Key_Escape:
- button_mask = CHIAKI_CONTROLLER_BUTTON_PS;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_Y_UP):
+ keyboard_state.left_y = press_event ? -0x7fff : 0;
break;
- case Qt::Key::Key_T:
- button_mask = CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_Y_DOWN):
+ keyboard_state.left_y = press_event ? 0x7fff : 0;
+ break;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_X_UP):
+ keyboard_state.left_x = press_event ? 0x7fff : 0;
+ break;
+ case static_cast(ControllerButtonExt::ANALOG_STICK_LEFT_X_DOWN):
+ keyboard_state.left_x = press_event ? -0x7fff : 0;
break;
default:
- // not interested
- return;
+ if(press_event)
+ keyboard_state.buttons |= button;
+ else
+ keyboard_state.buttons &= ~button;
+ break;
}
- if(event->type() == QEvent::KeyPress)
- keyboard_state.buttons |= button_mask;
- else
- keyboard_state.buttons &= ~button_mask;
-
SendFeedbackState();
}
void StreamSession::UpdateGamepads()
{
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- if(!gamepad || !gamepad->isConnected())
+#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
+ for(auto controller_id : controllers.keys())
{
- if(gamepad)
+ auto controller = controllers[controller_id];
+ if(!controller->IsConnected())
{
- CHIAKI_LOGI(log.GetChiakiLog(), "Gamepad %d disconnected", gamepad->deviceId());
- delete gamepad;
- gamepad = nullptr;
- }
- const auto connected_pads = QGamepadManager::instance()->connectedGamepads();
- if(!connected_pads.isEmpty())
- {
- gamepad = new QGamepad(connected_pads[0], this);
- CHIAKI_LOGI(log.GetChiakiLog(), "Gamepad %d connected: \"%s\"", connected_pads[0], gamepad->name().toLocal8Bit().constData());
- connect(gamepad, &QGamepad::buttonAChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonBChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonXChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonYChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonLeftChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonRightChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonUpChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonDownChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonL1Changed, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonR1Changed, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonL1Changed, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonL2Changed, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonL3Changed, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonR3Changed, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonStartChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonSelectChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::buttonGuideChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::axisLeftXChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::axisLeftYChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::axisRightXChanged, this, &StreamSession::SendFeedbackState);
- connect(gamepad, &QGamepad::axisRightYChanged, this, &StreamSession::SendFeedbackState);
+ CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d disconnected", controller->GetDeviceID());
+ controllers.remove(controller_id);
+ if(controller->IsDualSense())
+ DisconnectHaptics();
+ delete controller;
}
}
- SendFeedbackState();
-#endif
-#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
- if(!controller || !controller->IsConnected())
+ const auto available_controllers = ControllerManager::GetInstance()->GetAvailableControllers();
+ for(auto controller_id : available_controllers)
{
- if(controller)
+ if(!controllers.contains(controller_id))
{
- CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d disconnected", controller->GetDeviceID());
- delete controller;
- controller = nullptr;
- }
- const auto available_controllers = ControllerManager::GetInstance()->GetAvailableControllers();
- if(!available_controllers.isEmpty())
- {
- controller = ControllerManager::GetInstance()->OpenController(available_controllers[0]);
+ auto controller = ControllerManager::GetInstance()->OpenController(controller_id);
if(!controller)
{
- CHIAKI_LOGE(log.GetChiakiLog(), "Failed to open controller %d", available_controllers[0]);
- return;
+ CHIAKI_LOGE(log.GetChiakiLog(), "Failed to open controller %d", controller_id);
+ continue;
}
- CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d opened: \"%s\"", available_controllers[0], controller->GetName().toLocal8Bit().constData());
+ CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d opened: \"%s\"", controller_id, controller->GetName().toLocal8Bit().constData());
connect(controller, &Controller::StateChanged, this, &StreamSession::SendFeedbackState);
+ controllers[controller_id] = controller;
+ if(controller->IsDualSense())
+ {
+ // Connect haptics audio device with a delay to give the sound system time to set up
+ QTimer::singleShot(1000, this, &StreamSession::ConnectHaptics);
+ }
}
}
@@ -254,39 +379,19 @@ void StreamSession::UpdateGamepads()
void StreamSession::SendFeedbackState()
{
- ChiakiControllerState state = {};
+ ChiakiControllerState state;
+ chiaki_controller_state_set_idle(&state);
-#if CHIAKI_GUI_ENABLE_QT_GAMEPAD
- if(gamepad)
+#if CHIAKI_GUI_ENABLE_SETSU
+ // setsu is the one that potentially has gyro/accel/orient so copy that directly first
+ state = setsu_state;
+#endif
+
+ for(auto controller : controllers)
{
- state.buttons |= gamepad->buttonA() ? CHIAKI_CONTROLLER_BUTTON_CROSS : 0;
- state.buttons |= gamepad->buttonB() ? CHIAKI_CONTROLLER_BUTTON_MOON : 0;
- state.buttons |= gamepad->buttonX() ? CHIAKI_CONTROLLER_BUTTON_BOX : 0;
- state.buttons |= gamepad->buttonY() ? CHIAKI_CONTROLLER_BUTTON_PYRAMID : 0;
- state.buttons |= gamepad->buttonLeft() ? CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT : 0;
- state.buttons |= gamepad->buttonRight() ? CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT : 0;
- state.buttons |= gamepad->buttonUp() ? CHIAKI_CONTROLLER_BUTTON_DPAD_UP : 0;
- state.buttons |= gamepad->buttonDown() ? CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN : 0;
- state.buttons |= gamepad->buttonL1() ? CHIAKI_CONTROLLER_BUTTON_L1 : 0;
- state.buttons |= gamepad->buttonR1() ? CHIAKI_CONTROLLER_BUTTON_R1 : 0;
- state.buttons |= gamepad->buttonL3() ? CHIAKI_CONTROLLER_BUTTON_L3 : 0;
- state.buttons |= gamepad->buttonR3() ? CHIAKI_CONTROLLER_BUTTON_R3 : 0;
- state.buttons |= gamepad->buttonStart() ? CHIAKI_CONTROLLER_BUTTON_OPTIONS : 0;
- state.buttons |= gamepad->buttonSelect() ? CHIAKI_CONTROLLER_BUTTON_SHARE : 0;
- state.buttons |= gamepad->buttonGuide() ? CHIAKI_CONTROLLER_BUTTON_PS : 0;
- state.l2_state = (uint8_t)(gamepad->buttonL2() * 0xff);
- state.r2_state = (uint8_t)(gamepad->buttonR2() * 0xff);
- state.left_x = static_cast(gamepad->axisLeftX() * 0x7fff);
- state.left_y = static_cast(gamepad->axisLeftY() * 0x7fff);
- state.right_x = static_cast(gamepad->axisRightX() * 0x7fff);
- state.right_y = static_cast(gamepad->axisRightY() * 0x7fff);
+ auto controller_state = controller->GetState();
+ chiaki_controller_state_or(&state, &state, &controller_state);
}
-#endif
-
-#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
- if(controller)
- state = controller->GetState();
-#endif
chiaki_controller_state_or(&state, &state, &keyboard_state);
chiaki_session_set_controller_state(&session, &state);
@@ -305,7 +410,7 @@ void StreamSession::InitAudio(unsigned int channels, unsigned int rate)
audio_format.setCodec("audio/pcm");
audio_format.setSampleType(QAudioFormat::SignedInt);
- QAudioDeviceInfo audio_device_info(QAudioDeviceInfo::defaultOutputDevice());
+ QAudioDeviceInfo audio_device_info = audio_out_device_info;
if(!audio_device_info.isFormatSupported(audio_format))
{
CHIAKI_LOGE(log.GetChiakiLog(), "Audio Format with %u channels @ %u Hz not supported by Audio Device %s",
@@ -314,7 +419,7 @@ void StreamSession::InitAudio(unsigned int channels, unsigned int rate)
return;
}
- audio_output = new QAudioOutput(audio_format, this);
+ audio_output = new QAudioOutput(audio_device_info, audio_format, this);
audio_output->setBufferSize(audio_buffer_size);
audio_io = audio_output->start();
@@ -323,6 +428,82 @@ void StreamSession::InitAudio(unsigned int channels, unsigned int rate)
channels, rate, audio_output->bufferSize());
}
+void StreamSession::InitHaptics()
+{
+ haptics_output = 0;
+ haptics_resampler_buf = nullptr;
+#ifdef Q_OS_LINUX
+ // Haptics work most reliably with Pipewire, so try to use that if available
+ SDL_SetHint("SDL_AUDIODRIVER", "pipewire");
+#endif
+
+ if(SDL_Init(SDL_INIT_AUDIO) < 0)
+ {
+ CHIAKI_LOGE(log.GetChiakiLog(), "Could not initialize SDL Audio for haptics output: %s", SDL_GetError());
+ return;
+ }
+
+#ifdef Q_OS_LINUX
+ if(!strstr(SDL_GetCurrentAudioDriver(), "pipewire"))
+ {
+ CHIAKI_LOGW(
+ log.GetChiakiLog(),
+ "Haptics output is not using Pipewire, this may not work reliably. (was: '%s')",
+ SDL_GetCurrentAudioDriver());
+ }
+#endif
+
+ SDL_AudioCVT cvt;
+ SDL_BuildAudioCVT(&cvt, AUDIO_S16LSB, 4, 3000, AUDIO_S16LSB, 4, 48000);
+ cvt.len = 240; // 10 16bit stereo samples
+ haptics_resampler_buf = (uint8_t*) calloc(cvt.len * cvt.len_mult, sizeof(uint8_t));
+}
+
+void StreamSession::DisconnectHaptics()
+{
+ if(this->haptics_output > 0)
+ {
+ SDL_CloseAudioDevice(haptics_output);
+ this->haptics_output = 0;
+ }
+}
+
+void StreamSession::ConnectHaptics()
+{
+ if(this->haptics_output > 0)
+ {
+ CHIAKI_LOGW(this->log.GetChiakiLog(), "Haptics already connected to an attached DualSense controller, ignoring additional controllers.");
+ return;
+ }
+
+ SDL_AudioSpec want, have;
+ SDL_zero(want);
+ want.freq = 48000;
+ want.format = AUDIO_S16LSB;
+ want.channels = 4;
+ want.samples = 480; // 10ms buffer
+ want.callback = NULL;
+
+ const char *device_name = nullptr;
+ for(int i=0; i < SDL_GetNumAudioDevices(0); i++)
+ {
+ device_name = SDL_GetAudioDeviceName(i, 0);
+ if(!device_name || !strstr(device_name, DUALSENSE_AUDIO_DEVICE_NEEDLE))
+ continue;
+ haptics_output = SDL_OpenAudioDevice(device_name, 0, &want, &have, 0);
+ if(haptics_output == 0)
+ {
+ CHIAKI_LOGE(log.GetChiakiLog(), "Could not open SDL Audio Device %s for haptics output: %s", device_name, SDL_GetError());
+ continue;
+ }
+ SDL_PauseAudioDevice(haptics_output, 0);
+ CHIAKI_LOGI(log.GetChiakiLog(), "Haptics Audio Device '%s' opened with %d channels @ %d Hz, buffer size %u (driver=%s)", device_name, have.channels, have.freq, have.size, SDL_GetCurrentAudioDriver());
+ return;
+ }
+ CHIAKI_LOGW(log.GetChiakiLog(), "DualSense features were enabled and a DualSense is connected, but could not find the DualSense audio device!");
+ return;
+}
+
void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count)
{
if(!audio_io)
@@ -330,24 +511,188 @@ void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count)
audio_io->write((const char *)buf, static_cast(samples_count * 2 * 2));
}
-void StreamSession::PushVideoSample(uint8_t *buf, size_t buf_size)
+void StreamSession::PushHapticsFrame(uint8_t *buf, size_t buf_size)
{
- video_decoder.PushFrame(buf, buf_size);
+ if(haptics_output == 0)
+ return;
+ SDL_AudioCVT cvt;
+ // Haptics samples are coming in at 3KHZ, but the DualSense expects 48KHZ
+ SDL_BuildAudioCVT(&cvt, AUDIO_S16LSB, 4, 3000, AUDIO_S16LSB, 4, 48000);
+ cvt.len = buf_size * 2;
+ cvt.buf = haptics_resampler_buf;
+ // Remix to 4 channels
+ for (int i=0; i < buf_size; i+=4)
+ {
+ SDL_memset(haptics_resampler_buf + i * 2, 0, 4);
+ SDL_memcpy(haptics_resampler_buf + (i * 2) + 4, buf + i, 4);
+ }
+ // Resample to 48kHZ
+ if(SDL_ConvertAudio(&cvt) != 0)
+ {
+ CHIAKI_LOGE(log.GetChiakiLog(), "Failed to resample haptics audio: %s", SDL_GetError());
+ return;
+ }
+
+ if(SDL_QueueAudio(haptics_output, cvt.buf, cvt.len_cvt) < 0)
+ {
+ CHIAKI_LOGE(log.GetChiakiLog(), "Failed to submit haptics audio to device: %s", SDL_GetError());
+ return;
+ }
}
void StreamSession::Event(ChiakiEvent *event)
{
switch(event->type)
{
+ case CHIAKI_EVENT_CONNECTED:
+ connected = true;
+ break;
case CHIAKI_EVENT_QUIT:
+ connected = false;
emit SessionQuit(event->quit.reason, event->quit.reason_str ? QString::fromUtf8(event->quit.reason_str) : QString());
break;
case CHIAKI_EVENT_LOGIN_PIN_REQUEST:
emit LoginPINRequested(event->login_pin_request.pin_incorrect);
break;
+ case CHIAKI_EVENT_RUMBLE: {
+ uint8_t left = event->rumble.left;
+ uint8_t right = event->rumble.right;
+ QMetaObject::invokeMethod(this, [this, left, right]() {
+ for(auto controller : controllers)
+ controller->SetRumble(left, right);
+ });
+ break;
+ }
+ case CHIAKI_EVENT_TRIGGER_EFFECTS: {
+ uint8_t type_left = event->trigger_effects.type_left;
+ uint8_t data_left[10];
+ memcpy(data_left, event->trigger_effects.left, 10);
+ uint8_t data_right[10];
+ memcpy(data_right, event->trigger_effects.right, 10);
+ uint8_t type_right = event->trigger_effects.type_right;
+ QMetaObject::invokeMethod(this, [this, type_left, data_left, type_right, data_right]() {
+ for(auto controller : controllers)
+ controller->SetTriggerEffects(type_left, data_left, type_right, data_right);
+ });
+ break;
+ }
+ default:
+ break;
}
}
+#if CHIAKI_GUI_ENABLE_SETSU
+void StreamSession::HandleSetsuEvent(SetsuEvent *event)
+{
+ if(!setsu)
+ return;
+ switch(event->type)
+ {
+ case SETSU_EVENT_DEVICE_ADDED:
+ switch(event->dev_type)
+ {
+ case SETSU_DEVICE_TYPE_TOUCHPAD:
+ // connect all the touchpads!
+ if(setsu_connect(setsu, event->path, event->dev_type))
+ CHIAKI_LOGI(GetChiakiLog(), "Connected Setsu Touchpad Device %s", event->path);
+ else
+ CHIAKI_LOGE(GetChiakiLog(), "Failed to connect to Setsu Touchpad Device %s", event->path);
+ break;
+ case SETSU_DEVICE_TYPE_MOTION:
+ // connect only one motion since multiple make no sense
+ if(setsu_motion_device)
+ {
+ CHIAKI_LOGI(GetChiakiLog(), "Setsu Motion Device %s detected there is already one connected",
+ event->path);
+ break;
+ }
+ setsu_motion_device = setsu_connect(setsu, event->path, event->dev_type);
+ if(setsu_motion_device)
+ CHIAKI_LOGI(GetChiakiLog(), "Connected Setsu Motion Device %s", event->path);
+ else
+ CHIAKI_LOGE(GetChiakiLog(), "Failed to connect to Setsu Motion Device %s", event->path);
+ break;
+ }
+ break;
+ case SETSU_EVENT_DEVICE_REMOVED:
+ switch(event->dev_type)
+ {
+ case SETSU_DEVICE_TYPE_TOUCHPAD:
+ CHIAKI_LOGI(GetChiakiLog(), "Setsu Touchpad Device %s disconnected", event->path);
+ for(auto it=setsu_ids.begin(); it!=setsu_ids.end();)
+ {
+ if(it.key().first == event->path)
+ {
+ chiaki_controller_state_stop_touch(&setsu_state, it.value());
+ setsu_ids.erase(it++);
+ }
+ else
+ it++;
+ }
+ SendFeedbackState();
+ break;
+ case SETSU_DEVICE_TYPE_MOTION:
+ if(!setsu_motion_device || strcmp(setsu_device_get_path(setsu_motion_device), event->path))
+ break;
+ CHIAKI_LOGI(GetChiakiLog(), "Setsu Motion Device %s disconnected", event->path);
+ setsu_motion_device = nullptr;
+ chiaki_orientation_tracker_init(&orient_tracker);
+ orient_dirty = true;
+ break;
+ }
+ break;
+ case SETSU_EVENT_TOUCH_DOWN:
+ break;
+ case SETSU_EVENT_TOUCH_UP:
+ for(auto it=setsu_ids.begin(); it!=setsu_ids.end(); it++)
+ {
+ if(it.key().first == setsu_device_get_path(event->dev) && it.key().second == event->touch.tracking_id)
+ {
+ chiaki_controller_state_stop_touch(&setsu_state, it.value());
+ setsu_ids.erase(it);
+ break;
+ }
+ }
+ SendFeedbackState();
+ break;
+ case SETSU_EVENT_TOUCH_POSITION: {
+ QPair k = { setsu_device_get_path(event->dev), event->touch.tracking_id };
+ auto it = setsu_ids.find(k);
+ if(it == setsu_ids.end())
+ {
+ int8_t cid = chiaki_controller_state_start_touch(&setsu_state, event->touch.x, event->touch.y);
+ if(cid >= 0)
+ setsu_ids[k] = (uint8_t)cid;
+ else
+ break;
+ }
+ else
+ chiaki_controller_state_set_touch_pos(&setsu_state, it.value(), event->touch.x, event->touch.y);
+ SendFeedbackState();
+ break;
+ }
+ case SETSU_EVENT_BUTTON_DOWN:
+ setsu_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
+ break;
+ case SETSU_EVENT_BUTTON_UP:
+ setsu_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
+ break;
+ case SETSU_EVENT_MOTION:
+ chiaki_orientation_tracker_update(&orient_tracker,
+ event->motion.gyro_x, event->motion.gyro_y, event->motion.gyro_z,
+ event->motion.accel_x, event->motion.accel_y, event->motion.accel_z,
+ event->motion.timestamp);
+ orient_dirty = true;
+ break;
+ }
+}
+#endif
+
+void StreamSession::TriggerFfmpegFrameAvailable()
+{
+ emit FfmpegFrameAvailable();
+}
+
class StreamSessionPrivate
{
public:
@@ -357,8 +702,12 @@ class StreamSessionPrivate
}
static void PushAudioFrame(StreamSession *session, int16_t *buf, size_t samples_count) { session->PushAudioFrame(buf, samples_count); }
- static void PushVideoSample(StreamSession *session, uint8_t *buf, size_t buf_size) { session->PushVideoSample(buf, buf_size); }
+ static void PushHapticsFrame(StreamSession *session, uint8_t *buf, size_t buf_size) { session->PushHapticsFrame(buf, buf_size); }
static void Event(StreamSession *session, ChiakiEvent *event) { session->Event(event); }
+#if CHIAKI_GUI_ENABLE_SETSU
+ static void HandleSetsuEvent(StreamSession *session, SetsuEvent *event) { session->HandleSetsuEvent(event); }
+#endif
+ static void TriggerFfmpegFrameAvailable(StreamSession *session) { session->TriggerFfmpegFrameAvailable(); }
};
static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user)
@@ -373,15 +722,28 @@ static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user)
StreamSessionPrivate::PushAudioFrame(session, buf, samples_count);
}
-static bool VideoSampleCb(uint8_t *buf, size_t buf_size, void *user)
+static void HapticsFrameCb(uint8_t *buf, size_t buf_size, void *user)
{
auto session = reinterpret_cast(user);
- StreamSessionPrivate::PushVideoSample(session, buf, buf_size);
- return true;
+ StreamSessionPrivate::PushHapticsFrame(session, buf, buf_size);
}
static void EventCb(ChiakiEvent *event, void *user)
{
auto session = reinterpret_cast(user);
StreamSessionPrivate::Event(session, event);
-}
\ No newline at end of file
+}
+
+#if CHIAKI_GUI_ENABLE_SETSU
+static void SessionSetsuCb(SetsuEvent *event, void *user)
+{
+ auto session = reinterpret_cast(user);
+ StreamSessionPrivate::HandleSetsuEvent(session, event);
+}
+#endif
+
+static void FfmpegFrameCb(ChiakiFfmpegDecoder *decoder, void *user)
+{
+ auto session = reinterpret_cast(user);
+ StreamSessionPrivate::TriggerFfmpegFrameAvailable(session);
+}
diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp
index a993ebb..a2337d7 100644
--- a/gui/src/streamwindow.cpp
+++ b/gui/src/streamwindow.cpp
@@ -1,32 +1,20 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent)
- : QMainWindow(parent)
+ : QMainWindow(parent),
+ connect_info(connect_info)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(qApp->applicationName() + " | Stream");
@@ -36,7 +24,7 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget
try
{
- Init(connect_info);
+ Init();
}
catch(const Exception &e)
{
@@ -51,27 +39,85 @@ StreamWindow::~StreamWindow()
delete av_widget;
}
-void StreamWindow::Init(const StreamSessionConnectInfo &connect_info)
+#include
+
+void StreamWindow::Init()
{
session = new StreamSession(connect_info, this);
connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit);
connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested);
- av_widget = new AVOpenGLWidget(session->GetVideoDecoder(), this);
- setCentralWidget(av_widget);
+ const QKeySequence fullscreen_shortcut = Qt::Key_F11;
+ const QKeySequence stretch_shortcut = Qt::CTRL + Qt::Key_S;
+ const QKeySequence zoom_shortcut = Qt::CTRL + Qt::Key_Z;
+
+ fullscreen_action = new QAction(tr("Fullscreen"), this);
+ fullscreen_action->setCheckable(true);
+ fullscreen_action->setShortcut(fullscreen_shortcut);
+ addAction(fullscreen_action);
+ connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen);
+
+ if(session->GetFfmpegDecoder())
+ {
+ av_widget = new AVOpenGLWidget(session, this, connect_info.transform_mode);
+ setCentralWidget(av_widget);
+
+ av_widget->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(av_widget, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
+ av_widget->ResetMouseTimeout();
+
+ QMenu menu(av_widget);
+ menu.addAction(fullscreen_action);
+ menu.addSeparator();
+ menu.addAction(stretch_action);
+ menu.addAction(zoom_action);
+ releaseKeyboard();
+ connect(&menu, &QMenu::aboutToHide, this, [this] {
+ grabKeyboard();
+ });
+ menu.exec(av_widget->mapToGlobal(pos));
+ });
+ }
+ else
+ {
+ QWidget *bg_widget = new QWidget(this);
+ bg_widget->setStyleSheet("background-color: black;");
+ setCentralWidget(bg_widget);
+ }
grabKeyboard();
session->Start();
- auto fullscreen_action = new QAction(tr("Fullscreen"), this);
- fullscreen_action->setShortcut(Qt::Key_F11);
- addAction(fullscreen_action);
- connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen);
+ stretch_action = new QAction(tr("Stretch"), this);
+ stretch_action->setCheckable(true);
+ stretch_action->setShortcut(stretch_shortcut);
+ addAction(stretch_action);
+ connect(stretch_action, &QAction::triggered, this, &StreamWindow::ToggleStretch);
+
+ zoom_action = new QAction(tr("Zoom"), this);
+ zoom_action->setCheckable(true);
+ zoom_action->setShortcut(zoom_shortcut);
+ addAction(zoom_action);
+ connect(zoom_action, &QAction::triggered, this, &StreamWindow::ToggleZoom);
+
+ auto quit_action = new QAction(tr("Quit"), this);
+ quit_action->setShortcut(Qt::CTRL + Qt::Key_Q);
+ addAction(quit_action);
+ connect(quit_action, &QAction::triggered, this, &StreamWindow::Quit);
resize(connect_info.video_profile.width, connect_info.video_profile.height);
- show();
+
+ if(connect_info.fullscreen)
+ {
+ showFullScreen();
+ fullscreen_action->setChecked(true);
+ }
+ else
+ show();
+
+ UpdateTransformModeActions();
}
void StreamWindow::keyPressEvent(QKeyEvent *event)
@@ -86,15 +132,76 @@ void StreamWindow::keyReleaseEvent(QKeyEvent *event)
session->HandleKeyboardEvent(event);
}
-void StreamWindow::closeEvent(QCloseEvent *)
+void StreamWindow::Quit()
+{
+ close();
+}
+
+void StreamWindow::mousePressEvent(QMouseEvent *event)
+{
+ if(session && session->HandleMouseEvent(event))
+ return;
+ QMainWindow::mousePressEvent(event);
+}
+
+void StreamWindow::mouseReleaseEvent(QMouseEvent *event)
+{
+ if(session && session->HandleMouseEvent(event))
+ return;
+ QMainWindow::mouseReleaseEvent(event);
+}
+
+void StreamWindow::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ if(event->button() == Qt::MouseButton::LeftButton)
+ {
+ ToggleFullscreen();
+ return;
+ }
+ QMainWindow::mouseDoubleClickEvent(event);
+}
+
+void StreamWindow::closeEvent(QCloseEvent *event)
{
if(session)
+ {
+ if(session->IsConnected())
+ {
+ bool sleep = false;
+ switch(connect_info.settings->GetDisconnectAction())
+ {
+ case DisconnectAction::Ask: {
+ auto res = QMessageBox::question(this, tr("Disconnect Session"), tr("Do you want the Console to go into sleep mode?"),
+ QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
+ switch(res)
+ {
+ case QMessageBox::Yes:
+ sleep = true;
+ break;
+ case QMessageBox::Cancel:
+ event->ignore();
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+ case DisconnectAction::AlwaysSleep:
+ sleep = true;
+ break;
+ default:
+ break;
+ }
+ if(sleep)
+ session->GoToBed();
+ }
session->Stop();
+ }
}
void StreamWindow::SessionQuit(ChiakiQuitReason reason, const QString &reason_str)
{
- if(reason != CHIAKI_QUIT_REASON_STOPPED)
+ if(chiaki_quit_reason_is_error(reason))
{
QString m = tr("Chiaki Session has quit") + ":\n" + chiaki_quit_reason_string(reason);
if(!reason_str.isEmpty())
@@ -127,12 +234,74 @@ void StreamWindow::ToggleFullscreen()
{
if(isFullScreen())
{
- setCursor(Qt::ArrowCursor);
showNormal();
+ fullscreen_action->setChecked(false);
}
else
{
- setCursor(Qt::BlankCursor);
showFullScreen();
+ if(av_widget)
+ av_widget->HideMouse();
+ fullscreen_action->setChecked(true);
}
}
+
+void StreamWindow::UpdateTransformModeActions()
+{
+ TransformMode tm = av_widget ? av_widget->GetTransformMode() : TransformMode::Fit;
+ stretch_action->setChecked(tm == TransformMode::Stretch);
+ zoom_action->setChecked(tm == TransformMode::Zoom);
+}
+
+void StreamWindow::ToggleStretch()
+{
+ if(!av_widget)
+ return;
+ av_widget->SetTransformMode(
+ av_widget->GetTransformMode() == TransformMode::Stretch
+ ? TransformMode::Fit
+ : TransformMode::Stretch);
+ UpdateTransformModeActions();
+}
+
+void StreamWindow::ToggleZoom()
+{
+ if(!av_widget)
+ return;
+ av_widget->SetTransformMode(
+ av_widget->GetTransformMode() == TransformMode::Zoom
+ ? TransformMode::Fit
+ : TransformMode::Zoom);
+ UpdateTransformModeActions();
+}
+
+void StreamWindow::resizeEvent(QResizeEvent *event)
+{
+ UpdateVideoTransform();
+ QMainWindow::resizeEvent(event);
+}
+
+void StreamWindow::moveEvent(QMoveEvent *event)
+{
+ UpdateVideoTransform();
+ QMainWindow::moveEvent(event);
+}
+
+void StreamWindow::changeEvent(QEvent *event)
+{
+ if(event->type() == QEvent::ActivationChange)
+ UpdateVideoTransform();
+ QMainWindow::changeEvent(event);
+}
+
+void StreamWindow::UpdateVideoTransform()
+{
+#if CHIAKI_LIB_ENABLE_PI_DECODER
+ ChiakiPiDecoder *pi_decoder = session->GetPiDecoder();
+ if(pi_decoder)
+ {
+ QRect r = geometry();
+ chiaki_pi_decoder_set_params(pi_decoder, r.x(), r.y(), r.width(), r.height(), isActiveWindow());
+ }
+#endif
+}
diff --git a/gui/src/videodecoder.cpp b/gui/src/videodecoder.cpp
deleted file mode 100644
index bd3f965..0000000
--- a/gui/src/videodecoder.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
-
-#include
-
-#include
-
-#include
-
-VideoDecoder::VideoDecoder(ChiakiLog *log) : log(log)
-{
- codec = avcodec_find_decoder(AV_CODEC_ID_H264);
- if(!codec)
- throw VideoDecoderException("H264 Codec not available");
-
- codec_context = avcodec_alloc_context3(codec);
- if(!codec_context)
- throw VideoDecoderException("Failed to alloc codec context");
-
- if(avcodec_open2(codec_context, codec, nullptr) < 0)
- {
- avcodec_free_context(&codec_context);
- throw VideoDecoderException("Failed to open codec context");
- }
-}
-
-VideoDecoder::~VideoDecoder()
-{
- avcodec_close(codec_context);
- avcodec_free_context(&codec_context);
-}
-
-void VideoDecoder::PushFrame(uint8_t *buf, size_t buf_size)
-{
- {
- QMutexLocker locker(&mutex);
-
- AVPacket packet;
- av_init_packet(&packet);
- packet.data = buf;
- packet.size = buf_size;
- int r;
-send_packet:
- r = avcodec_send_packet(codec_context, &packet);
- if(r != 0)
- {
- if(r == AVERROR(EAGAIN))
- {
- CHIAKI_LOGE(log, "AVCodec internal buffer is full removing frames before pushing");
- AVFrame *frame = av_frame_alloc();
- if(!frame)
- {
- CHIAKI_LOGE(log, "Failed to alloc AVFrame");
- return;
- }
- r = avcodec_receive_frame(codec_context, frame);
- av_frame_free(&frame);
- if(r != 0)
- {
- CHIAKI_LOGE(log, "Failed to pull frame");
- return;
- }
- goto send_packet;
- }
- else
- {
- char errbuf[128];
- av_make_error_string(errbuf, sizeof(errbuf), r);
- CHIAKI_LOGE(log, "Failed to push frame: %s", errbuf);
- return;
- }
- }
- }
-
- emit FramesAvailable();
-}
-
-AVFrame *VideoDecoder::PullFrame()
-{
- QMutexLocker locker(&mutex);
-
- // always try to pull as much as possible and return only the very last frame
- AVFrame *frame_last = nullptr;
- AVFrame *frame = nullptr;
- while(true)
- {
- AVFrame *next_frame;
- if(frame_last)
- {
- av_frame_unref(frame_last);
- next_frame = frame_last;
- }
- else
- {
- next_frame = av_frame_alloc();
- if(!next_frame)
- return frame;
- }
- frame_last = frame;
- frame = next_frame;
- int r = avcodec_receive_frame(codec_context, frame);
- if(r != 0)
- {
- if(r != AVERROR(EAGAIN))
- CHIAKI_LOGE(log, "Decoding with FFMPEG failed");
- av_frame_free(&frame);
- return frame_last;
- }
- }
-}
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index cfb2c04..cbfd6b0 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -21,6 +21,7 @@ set(HEADER_FILES
include/chiaki/video.h
include/chiaki/videoreceiver.h
include/chiaki/frameprocessor.h
+ include/chiaki/packetstats.h
include/chiaki/seqnum.h
include/chiaki/discovery.h
include/chiaki/congestioncontrol.h
@@ -34,7 +35,8 @@ set(HEADER_FILES
include/chiaki/time.h
include/chiaki/fec.h
include/chiaki/regist.h
- include/chiaki/opusdecoder.h)
+ include/chiaki/opusdecoder.h
+ include/chiaki/orientation.h)
set(SOURCE_FILES
src/common.c
@@ -59,6 +61,7 @@ set(SOURCE_FILES
src/audioreceiver.c
src/videoreceiver.c
src/frameprocessor.c
+ src/packetstats.c
src/discovery.c
src/congestioncontrol.c
src/stoppipe.c
@@ -69,12 +72,24 @@ set(SOURCE_FILES
src/controller.c
src/takionsendbuffer.c
src/time.c
- src/fec
+ src/fec.c
src/regist.c
- src/opusdecoder.c)
+ src/opusdecoder.c
+ src/orientation.c)
+
+if(CHIAKI_ENABLE_FFMPEG_DECODER)
+ list(APPEND HEADER_FILES include/chiaki/ffmpegdecoder.h)
+ list(APPEND SOURCE_FILES src/ffmpegdecoder.c)
+endif()
+set(CHIAKI_LIB_ENABLE_PI_DECODER "${CHIAKI_ENABLE_FFMPEG_DECODER}")
+
+if(CHIAKI_ENABLE_PI_DECODER)
+ list(APPEND HEADER_FILES include/chiaki/pidecoder.h)
+ list(APPEND SOURCE_FILES src/pidecoder.c)
+endif()
+set(CHIAKI_LIB_ENABLE_PI_DECODER "${CHIAKI_ENABLE_PI_DECODER}")
add_subdirectory(protobuf)
-include_directories("${NANOPB_SOURCE_DIR}")
set_source_files_properties(${CHIAKI_LIB_PROTO_SOURCE_FILES} ${CHIAKI_LIB_PROTO_HEADER_FILES} PROPERTIES GENERATED TRUE)
include_directories("${CHIAKI_LIB_PROTO_INCLUDE_DIR}")
@@ -87,6 +102,10 @@ add_library(chiaki-lib ${HEADER_FILES} ${SOURCE_FILES} ${CHIAKI_LIB_PROTO_SOURCE
configure_file(config.h.in include/chiaki/config.h)
target_include_directories(chiaki-lib PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include")
+if(CMAKE_C_COMPILER_ID STREQUAL GNU OR CMAKE_C_COMPILER_ID STREQUAL Clang)
+ target_compile_options(chiaki-lib PRIVATE -Wall)
+endif()
+
add_dependencies(chiaki-lib chiaki-pb)
set_target_properties(chiaki-lib PROPERTIES OUTPUT_NAME chiaki)
@@ -99,16 +118,35 @@ target_include_directories(chiaki-lib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/includ
find_package(Threads REQUIRED)
target_link_libraries(chiaki-lib Threads::Threads)
-if(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT)
+if(CHIAKI_LIB_ENABLE_MBEDTLS)
+ if(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT)
+ target_link_libraries(chiaki-lib mbedtls mbedx509 mbedcrypto)
+ else()
+ # provided by mbedtls-static (mbedtls-devel)
+ find_library(MBEDTLS mbedtls)
+ find_library(MBEDX509 mbedx509)
+ find_library(MBEDCRYPTO mbedcrypto)
+ target_link_libraries(chiaki-lib ${MBEDTLS} ${MBEDX509} ${MBEDCRYPTO})
+ endif()
+elseif(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT)
target_link_libraries(chiaki-lib OpenSSL_Crypto)
else()
+ # default
find_package(OpenSSL REQUIRED)
target_link_libraries(chiaki-lib OpenSSL::Crypto)
endif()
-target_link_libraries(chiaki-lib protobuf-nanopb-static)
-target_link_libraries(chiaki-lib jerasure)
+target_link_libraries(chiaki-lib Nanopb::nanopb)
+target_link_libraries(chiaki-lib Jerasure::Jerasure)
+
+if(CHIAKI_ENABLE_FFMPEG_DECODER)
+ target_link_libraries(chiaki-lib FFMPEG::avcodec FFMPEG::avutil)
+endif()
+
+if(CHIAKI_ENABLE_PI_DECODER)
+ target_link_libraries(chiaki-lib ILClient::ILClient)
+endif()
if(CHIAKI_LIB_ENABLE_OPUS)
target_link_libraries(chiaki-lib ${Opus_LIBRARIES})
-endif()
\ No newline at end of file
+endif()
diff --git a/lib/config.h.in b/lib/config.h.in
index d7501e0..ecf1ab5 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -1,23 +1,9 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_CONFIG_H
#define CHIAKI_CONFIG_H
#cmakedefine01 CHIAKI_LIB_ENABLE_OPUS
+#cmakedefine01 CHIAKI_LIB_ENABLE_PI_DECODER
#endif // CHIAKI_CONFIG_H
diff --git a/lib/include/chiaki/audio.h b/lib/include/chiaki/audio.h
index 1480b96..3d454f8 100644
--- a/lib/include/chiaki/audio.h
+++ b/lib/include/chiaki/audio.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_AUDIO_H
#define CHIAKI_AUDIO_H
diff --git a/lib/include/chiaki/audioreceiver.h b/lib/include/chiaki/audioreceiver.h
index 171cabc..228b27c 100644
--- a/lib/include/chiaki/audioreceiver.h
+++ b/lib/include/chiaki/audioreceiver.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_AUDIORECEIVER_H
#define CHIAKI_AUDIORECEIVER_H
@@ -23,6 +8,7 @@
#include "audio.h"
#include "takion.h"
#include "thread.h"
+#include "packetstats.h"
#ifdef __cplusplus
extern "C" {
@@ -48,19 +34,20 @@ typedef struct chiaki_audio_receiver_t
ChiakiMutex mutex;
ChiakiSeqNum16 frame_index_prev;
bool frame_index_startup; // whether frame_index_prev has definitely not wrapped yet
+ ChiakiPacketStats *packet_stats;
} ChiakiAudioReceiver;
-CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *audio_receiver, struct chiaki_session_t *session);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *audio_receiver, struct chiaki_session_t *session, ChiakiPacketStats *packet_stats);
CHIAKI_EXPORT void chiaki_audio_receiver_fini(ChiakiAudioReceiver *audio_receiver);
CHIAKI_EXPORT void chiaki_audio_receiver_stream_info(ChiakiAudioReceiver *audio_receiver, ChiakiAudioHeader *audio_header);
CHIAKI_EXPORT void chiaki_audio_receiver_av_packet(ChiakiAudioReceiver *audio_receiver, ChiakiTakionAVPacket *packet);
-static inline ChiakiAudioReceiver *chiaki_audio_receiver_new(struct chiaki_session_t *session)
+static inline ChiakiAudioReceiver *chiaki_audio_receiver_new(struct chiaki_session_t *session, ChiakiPacketStats *packet_stats)
{
ChiakiAudioReceiver *audio_receiver = CHIAKI_NEW(ChiakiAudioReceiver);
if(!audio_receiver)
return NULL;
- ChiakiErrorCode err = chiaki_audio_receiver_init(audio_receiver, session);
+ ChiakiErrorCode err = chiaki_audio_receiver_init(audio_receiver, session, packet_stats);
if(err != CHIAKI_ERR_SUCCESS)
{
free(audio_receiver);
diff --git a/lib/include/chiaki/base64.h b/lib/include/chiaki/base64.h
index 92b2ca1..3c44b4b 100644
--- a/lib/include/chiaki/base64.h
+++ b/lib/include/chiaki/base64.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_BASE64_H
#define CHIAKI_BASE64_H
diff --git a/lib/include/chiaki/common.h b/lib/include/chiaki/common.h
index a2e87a4..630c954 100644
--- a/lib/include/chiaki/common.h
+++ b/lib/include/chiaki/common.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_COMMON_H
#define CHIAKI_COMMON_H
@@ -26,6 +11,7 @@
#include
#include
+#include
#ifdef __cplusplus
extern "C" {
@@ -64,7 +50,8 @@ typedef enum
CHIAKI_ERR_INVALID_RESPONSE,
CHIAKI_ERR_INVALID_MAC,
CHIAKI_ERR_UNINITIALIZED,
- CHIAKI_ERR_FEC_FAILED
+ CHIAKI_ERR_FEC_FAILED,
+ CHIAKI_ERR_VERSION_MISMATCH
} ChiakiErrorCode;
CHIAKI_EXPORT const char *chiaki_error_string(ChiakiErrorCode code);
@@ -72,11 +59,50 @@ CHIAKI_EXPORT const char *chiaki_error_string(ChiakiErrorCode code);
CHIAKI_EXPORT void *chiaki_aligned_alloc(size_t alignment, size_t size);
CHIAKI_EXPORT void chiaki_aligned_free(void *ptr);
+typedef enum
+{
+ // values must not change
+ CHIAKI_TARGET_PS4_UNKNOWN = 0,
+ CHIAKI_TARGET_PS4_8 = 800,
+ CHIAKI_TARGET_PS4_9 = 900,
+ CHIAKI_TARGET_PS4_10 = 1000,
+ CHIAKI_TARGET_PS5_UNKNOWN = 1000000,
+ CHIAKI_TARGET_PS5_1 = 1000100
+} ChiakiTarget;
+
+static inline bool chiaki_target_is_unknown(ChiakiTarget target)
+{
+ return target == CHIAKI_TARGET_PS5_UNKNOWN
+ || target == CHIAKI_TARGET_PS4_UNKNOWN;
+}
+
+static inline bool chiaki_target_is_ps5(ChiakiTarget target) { return target >= CHIAKI_TARGET_PS5_UNKNOWN; }
+
/**
* Perform initialization of global state needed for using the Chiaki lib
*/
CHIAKI_EXPORT ChiakiErrorCode chiaki_lib_init();
+typedef enum
+{
+ // values must not change
+ CHIAKI_CODEC_H264 = 0,
+ CHIAKI_CODEC_H265 = 1,
+ CHIAKI_CODEC_H265_HDR = 2
+} ChiakiCodec;
+
+static inline bool chiaki_codec_is_h265(ChiakiCodec codec)
+{
+ return codec == CHIAKI_CODEC_H265 || codec == CHIAKI_CODEC_H265_HDR;
+}
+
+static inline bool chiaki_codec_is_hdr(ChiakiCodec codec)
+{
+ return codec == CHIAKI_CODEC_H265_HDR;
+}
+
+CHIAKI_EXPORT const char *chiaki_codec_name(ChiakiCodec codec);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/include/chiaki/config.h b/lib/include/chiaki/config.h
new file mode 100644
index 0000000..542ff64
--- /dev/null
+++ b/lib/include/chiaki/config.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef CHIAKI_CONFIG_H
+#define CHIAKI_CONFIG_H
+
+#define CHIAKI_LIB_ENABLE_OPUS 1
+#define CHIAKI_LIB_ENABLE_PI_DECODER 0
+
+#endif // CHIAKI_CONFIG_H
diff --git a/lib/include/chiaki/congestioncontrol.h b/lib/include/chiaki/congestioncontrol.h
index 424396a..3760399 100644
--- a/lib/include/chiaki/congestioncontrol.h
+++ b/lib/include/chiaki/congestioncontrol.h
@@ -1,25 +1,11 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_CONGESTIONCONTROL_H
#define CHIAKI_CONGESTIONCONTROL_H
#include "takion.h"
#include "thread.h"
+#include "packetstats.h"
#ifdef __cplusplus
extern "C" {
@@ -28,11 +14,12 @@ extern "C" {
typedef struct chiaki_congestion_control_t
{
ChiakiTakion *takion;
+ ChiakiPacketStats *stats;
ChiakiThread thread;
ChiakiBoolPredCond stop_cond;
} ChiakiCongestionControl;
-CHIAKI_EXPORT ChiakiErrorCode chiaki_congestion_control_start(ChiakiCongestionControl *control, ChiakiTakion *takion);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_congestion_control_start(ChiakiCongestionControl *control, ChiakiTakion *takion, ChiakiPacketStats *stats);
/**
* Stop control and join the thread
diff --git a/lib/include/chiaki/controller.h b/lib/include/chiaki/controller.h
index 0239c6c..24e86a9 100644
--- a/lib/include/chiaki/controller.h
+++ b/lib/include/chiaki/controller.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_CONTROLLER_H
#define CHIAKI_CONTROLLER_H
@@ -56,6 +41,14 @@ typedef enum chiaki_controller_analog_button_t
CHIAKI_CONTROLLER_ANALOG_BUTTON_R2 = (1 << 17)
} ChiakiControllerAnalogButton;
+typedef struct chiaki_controller_touch_t
+{
+ uint16_t x, y;
+ int8_t id; // -1 = up
+} ChiakiControllerTouch;
+
+#define CHIAKI_CONTROLLER_TOUCHES_MAX 2
+
typedef struct chiaki_controller_state_t
{
/**
@@ -70,21 +63,32 @@ typedef struct chiaki_controller_state_t
int16_t left_y;
int16_t right_x;
int16_t right_y;
+
+ uint8_t touch_id_next;
+ ChiakiControllerTouch touches[CHIAKI_CONTROLLER_TOUCHES_MAX];
+
+ float gyro_x, gyro_y, gyro_z;
+ float accel_x, accel_y, accel_z;
+ float orient_x, orient_y, orient_z, orient_w;
} ChiakiControllerState;
CHIAKI_EXPORT void chiaki_controller_state_set_idle(ChiakiControllerState *state);
-static inline bool chiaki_controller_state_equals(ChiakiControllerState *a, ChiakiControllerState *b)
-{
- return a->buttons == b->buttons
- && a->l2_state == b->l2_state
- && a->r2_state == b->r2_state
- && a->left_x == b->left_x
- && a->left_y == b->left_y
- && a->right_x == b->right_x
- && a->right_y == b->right_y;
-}
+/**
+ * @return A non-negative newly allocated touch id allocated or -1 if there are no slots left
+ */
+CHIAKI_EXPORT int8_t chiaki_controller_state_start_touch(ChiakiControllerState *state, uint16_t x, uint16_t y);
+CHIAKI_EXPORT void chiaki_controller_state_stop_touch(ChiakiControllerState *state, uint8_t id);
+
+CHIAKI_EXPORT void chiaki_controller_state_set_touch_pos(ChiakiControllerState *state, uint8_t id, uint16_t x, uint16_t y);
+
+CHIAKI_EXPORT bool chiaki_controller_state_equals(ChiakiControllerState *a, ChiakiControllerState *b);
+
+/**
+ * Union of two controller states.
+ * Ignores gyro, accel and orient because it makes no sense there.
+ */
CHIAKI_EXPORT void chiaki_controller_state_or(ChiakiControllerState *out, ChiakiControllerState *a, ChiakiControllerState *b);
#ifdef __cplusplus
diff --git a/lib/include/chiaki/ctrl.h b/lib/include/chiaki/ctrl.h
index 71b0169..4152071 100644
--- a/lib/include/chiaki/ctrl.h
+++ b/lib/include/chiaki/ctrl.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_CTRL_H
#define CHIAKI_CTRL_H
@@ -33,6 +18,8 @@
extern "C" {
#endif
+typedef struct chiaki_ctrl_message_queue_t ChiakiCtrlMessageQueue;
+
typedef struct chiaki_ctrl_t
{
struct chiaki_session_t *session;
@@ -42,6 +29,7 @@ typedef struct chiaki_ctrl_t
bool login_pin_entered;
uint8_t *login_pin;
size_t login_pin_size;
+ ChiakiCtrlMessageQueue *msg_queue;
ChiakiStopPipe notif_pipe;
ChiakiMutex notif_mutex;
@@ -57,12 +45,20 @@ typedef struct chiaki_ctrl_t
size_t recv_buf_size;
uint64_t crypt_counter_local;
uint64_t crypt_counter_remote;
+ uint32_t keyboard_text_counter;
} ChiakiCtrl;
-CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, struct chiaki_session_t *session);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_init(ChiakiCtrl *ctrl, struct chiaki_session_t *session);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl);
CHIAKI_EXPORT void chiaki_ctrl_stop(ChiakiCtrl *ctrl);
CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_join(ChiakiCtrl *ctrl);
+CHIAKI_EXPORT void chiaki_ctrl_fini(ChiakiCtrl *ctrl);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_send_message(ChiakiCtrl *ctrl, uint16_t type, const uint8_t *payload, size_t payload_size);
CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, const uint8_t *pin, size_t pin_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_goto_bed(ChiakiCtrl *ctrl);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_keyboard_set_text(ChiakiCtrl *ctrl, const char* text);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_keyboard_accept(ChiakiCtrl *ctrl);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_keyboard_reject(ChiakiCtrl *ctrl);
#ifdef __cplusplus
}
diff --git a/lib/include/chiaki/discovery.h b/lib/include/chiaki/discovery.h
index 8694ac6..f26200b 100644
--- a/lib/include/chiaki/discovery.h
+++ b/lib/include/chiaki/discovery.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_DISCOVERY_H
#define CHIAKI_DISCOVERY_H
@@ -36,8 +21,12 @@ typedef unsigned short sa_family_t;
extern "C" {
#endif
-#define CHIAKI_DISCOVERY_PORT 987
-#define CHIAKI_DISCOVERY_PROTOCOL_VERSION "00020020"
+#define CHIAKI_DISCOVERY_PORT_PS4 987
+#define CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS4 "00020020"
+#define CHIAKI_DISCOVERY_PORT_PS5 9302
+#define CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5 "00030010"
+#define CHIAKI_DISCOVERY_PORT_LOCAL_MIN 9303
+#define CHIAKI_DISCOVERY_PORT_LOCAL_MAX 9319
typedef enum chiaki_discovery_cmd_t
{
@@ -84,6 +73,9 @@ typedef struct chiaki_discovery_host_t
#undef STRING_MEMBER
} ChiakiDiscoveryHost;
+CHIAKI_EXPORT bool chiaki_discovery_host_is_ps5(ChiakiDiscoveryHost *host);
+
+CHIAKI_EXPORT ChiakiTarget chiaki_discovery_host_system_version_target(ChiakiDiscoveryHost *host);
CHIAKI_EXPORT int chiaki_discovery_packet_fmt(char *buf, size_t buf_size, ChiakiDiscoveryPacket *packet);
@@ -116,7 +108,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_thread_stop(ChiakiDiscoveryThread
* Convenience function to send a wakeup packet
* @param discovery Discovery to send the packet on. May be NULL, in which case a new temporary Discovery will be created
*/
-CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_wakeup(ChiakiLog *log, ChiakiDiscovery *discovery, const char *host, uint64_t user_credential);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_wakeup(ChiakiLog *log, ChiakiDiscovery *discovery, const char *host, uint64_t user_credential, bool ps5);
#ifdef __cplusplus
}
diff --git a/lib/include/chiaki/discoveryservice.h b/lib/include/chiaki/discoveryservice.h
index 497f413..8cebda4 100644
--- a/lib/include/chiaki/discoveryservice.h
+++ b/lib/include/chiaki/discoveryservice.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_DISCOVERYSERVICE_H
diff --git a/lib/include/chiaki/ecdh.h b/lib/include/chiaki/ecdh.h
index 440fcb8..018457f 100644
--- a/lib/include/chiaki/ecdh.h
+++ b/lib/include/chiaki/ecdh.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_ECDH_H
#define CHIAKI_ECDH_H
@@ -27,12 +12,28 @@
extern "C" {
#endif
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+#include "mbedtls/ecdh.h"
+#include "mbedtls/ctr_drbg.h"
+#endif
+
+
#define CHIAKI_ECDH_SECRET_SIZE 32
typedef struct chiaki_ecdh_t
{
+// the following lines may lead to memory corruption
+// CHIAKI_LIB_ENABLE_MBEDTLS must be defined
+// globally (whole project)
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ // mbedtls ecdh context
+ mbedtls_ecdh_context ctx;
+ // deterministic random bit generator
+ mbedtls_ctr_drbg_context drbg;
+#else
struct ec_group_st *group;
struct ec_key_st *key_local;
+#endif
} ChiakiECDH;
CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_init(ChiakiECDH *ecdh);
diff --git a/lib/include/chiaki/fec.h b/lib/include/chiaki/fec.h
index 105d3e7..2a4b789 100644
--- a/lib/include/chiaki/fec.h
+++ b/lib/include/chiaki/fec.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_FEC_H
#define CHIAKI_FEC_H
@@ -31,7 +16,7 @@ extern "C" {
#define CHIAKI_FEC_WORDSIZE 8
-CHIAKI_EXPORT ChiakiErrorCode chiaki_fec_decode(uint8_t *frame_buf, size_t unit_size, unsigned int k, unsigned int m, const unsigned int *erasures, size_t erasures_count);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_fec_decode(uint8_t *frame_buf, size_t unit_size, size_t stride, unsigned int k, unsigned int m, const unsigned int *erasures, size_t erasures_count);
#ifdef __cplusplus
}
diff --git a/lib/include/chiaki/feedback.h b/lib/include/chiaki/feedback.h
index 6a74ae1..be66384 100644
--- a/lib/include/chiaki/feedback.h
+++ b/lib/include/chiaki/feedback.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_FEEDBACK_H
#define CHIAKI_FEEDBACK_H
@@ -30,21 +15,32 @@ extern "C" {
typedef struct chiaki_feedback_state_t
{
+ float gyro_x, gyro_y, gyro_z;
+ float accel_x, accel_y, accel_z;
+ float orient_x, orient_y, orient_z, orient_w;
int16_t left_x;
int16_t left_y;
int16_t right_x;
int16_t right_y;
} ChiakiFeedbackState;
-#define CHIAKI_FEEDBACK_STATE_BUF_SIZE 0x19
+#define CHIAKI_FEEDBACK_STATE_BUF_SIZE_MAX 0x1c
+
+#define CHIAKI_FEEDBACK_STATE_BUF_SIZE_V9 0x19
/**
- * @param buf buffer of at least CHIAKI_FEEDBACK_STATE_BUF_SIZE
+ * @param buf buffer of at least CHIAKI_FEEDBACK_STATE_BUF_SIZE_V9
*/
-CHIAKI_EXPORT void chiaki_feedback_state_format(uint8_t *buf, ChiakiFeedbackState *state);
+CHIAKI_EXPORT void chiaki_feedback_state_format_v9(uint8_t *buf, ChiakiFeedbackState *state);
+#define CHIAKI_FEEDBACK_STATE_BUF_SIZE_V12 0x1c
-#define CHIAKI_HISTORY_EVENT_SIZE_MAX 0x3 // TODO: will be bigger later for touchpad at least
+/**
+ * @param buf buffer of at least CHIAKI_FEEDBACK_STATE_BUF_SIZE_V12
+ */
+CHIAKI_EXPORT void chiaki_feedback_state_format_v12(uint8_t *buf, ChiakiFeedbackState *state);
+
+#define CHIAKI_HISTORY_EVENT_SIZE_MAX 0x5
typedef struct chiaki_feedback_history_event_t
{
@@ -58,6 +54,14 @@ typedef struct chiaki_feedback_history_event_t
*/
CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_history_event_set_button(ChiakiFeedbackHistoryEvent *event, uint64_t button, uint8_t state);
+/**
+ * @param pointer_id identifier for the touch from 0 to 127
+ * @param x from 0 to 1920
+ * @param y from 0 to 942
+ */
+CHIAKI_EXPORT void chiaki_feedback_history_event_set_touchpad(ChiakiFeedbackHistoryEvent *event,
+ bool down, uint8_t pointer_id, uint16_t x, uint16_t y);
+
/**
* Ring buffer of ChiakiFeedbackHistoryEvent
*/
diff --git a/lib/include/chiaki/feedbacksender.h b/lib/include/chiaki/feedbacksender.h
index 1e6e6c7..da202e8 100644
--- a/lib/include/chiaki/feedbacksender.h
+++ b/lib/include/chiaki/feedbacksender.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_FEEDBACKSENDER_H
#define CHIAKI_FEEDBACKSENDER_H
diff --git a/lib/include/chiaki/ffmpegdecoder.h b/lib/include/chiaki/ffmpegdecoder.h
new file mode 100644
index 0000000..1389ca0
--- /dev/null
+++ b/lib/include/chiaki/ffmpegdecoder.h
@@ -0,0 +1,43 @@
+#ifndef CHIAKI_FFMPEG_DECODER_H
+#define CHIAKI_FFMPEG_DECODER_H
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+typedef struct chiaki_ffmpeg_decoder_t ChiakiFfmpegDecoder;
+
+typedef void (*ChiakiFfmpegFrameAvailable)(ChiakiFfmpegDecoder *decover, void *user);
+
+struct chiaki_ffmpeg_decoder_t
+{
+ ChiakiLog *log;
+ ChiakiMutex mutex;
+ AVCodec *av_codec;
+ AVCodecContext *codec_context;
+ enum AVPixelFormat hw_pix_fmt;
+ AVBufferRef *hw_device_ctx;
+ ChiakiMutex cb_mutex;
+ ChiakiFfmpegFrameAvailable frame_available_cb;
+ void *frame_available_cb_user;
+};
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ffmpeg_decoder_init(ChiakiFfmpegDecoder *decoder, ChiakiLog *log,
+ ChiakiCodec codec, const char *hw_decoder_name,
+ ChiakiFfmpegFrameAvailable frame_available_cb, void *frame_available_cb_user);
+CHIAKI_EXPORT void chiaki_ffmpeg_decoder_fini(ChiakiFfmpegDecoder *decoder);
+CHIAKI_EXPORT bool chiaki_ffmpeg_decoder_video_sample_cb(uint8_t *buf, size_t buf_size, void *user);
+CHIAKI_EXPORT AVFrame *chiaki_ffmpeg_decoder_pull_frame(ChiakiFfmpegDecoder *decoder);
+CHIAKI_EXPORT enum AVPixelFormat chiaki_ffmpeg_decoder_get_pixel_format(ChiakiFfmpegDecoder *decoder);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CHIAKI_FFMPEG_DECODER_H
diff --git a/lib/include/chiaki/frameprocessor.h b/lib/include/chiaki/frameprocessor.h
index aae7172..bcac03e 100644
--- a/lib/include/chiaki/frameprocessor.h
+++ b/lib/include/chiaki/frameprocessor.h
@@ -1,25 +1,11 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_FRAMEPROCESSOR_H
#define CHIAKI_FRAMEPROCESSOR_H
#include "common.h"
#include "takion.h"
+#include "packetstats.h"
#include
#include
@@ -28,6 +14,16 @@
extern "C" {
#endif
+typedef struct chiaki_stream_stats_t
+{
+ uint64_t frames;
+ uint64_t bytes;
+} ChiakiStreamStats;
+
+CHIAKI_EXPORT void chiaki_stream_stats_reset(ChiakiStreamStats *stats);
+CHIAKI_EXPORT void chiaki_stream_stats_frame(ChiakiStreamStats *stats, uint64_t size);
+CHIAKI_EXPORT uint64_t chiaki_stream_stats_bitrate(ChiakiStreamStats *stats, uint64_t framerate);
+
struct chiaki_frame_unit_t;
typedef struct chiaki_frame_unit_t ChiakiFrameUnit;
@@ -37,12 +33,15 @@ typedef struct chiaki_frame_processor_t
uint8_t *frame_buf;
size_t frame_buf_size;
size_t buf_size_per_unit;
+ size_t buf_stride_per_unit;
unsigned int units_source_expected;
unsigned int units_fec_expected;
unsigned int units_source_received;
unsigned int units_fec_received;
ChiakiFrameUnit *unit_slots;
size_t unit_slots_size;
+ bool flushed; // whether we have already flushed the current frame, i.e. are only interested in stats, not data.
+ ChiakiStreamStats stream_stats;
} ChiakiFrameProcessor;
typedef enum chiaki_frame_flush_result_t {
@@ -55,6 +54,7 @@ typedef enum chiaki_frame_flush_result_t {
CHIAKI_EXPORT void chiaki_frame_processor_init(ChiakiFrameProcessor *frame_processor, ChiakiLog *log);
CHIAKI_EXPORT void chiaki_frame_processor_fini(ChiakiFrameProcessor *frame_processor);
+CHIAKI_EXPORT void chiaki_frame_processor_report_packet_stats(ChiakiFrameProcessor *frame_processor, ChiakiPacketStats *packet_stats);
CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_alloc_frame(ChiakiFrameProcessor *frame_processor, ChiakiTakionAVPacket *packet);
CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_put_unit(ChiakiFrameProcessor *frame_processor, ChiakiTakionAVPacket *packet);
diff --git a/lib/include/chiaki/gkcrypt.h b/lib/include/chiaki/gkcrypt.h
index a2c76e7..591b243 100644
--- a/lib/include/chiaki/gkcrypt.h
+++ b/lib/include/chiaki/gkcrypt.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_GKCRYPT_H
#define CHIAKI_GKCRYPT_H
@@ -35,15 +20,20 @@ extern "C" {
#define CHIAKI_GKCRYPT_GMAC_KEY_REFRESH_KEY_POS 45000
#define CHIAKI_GKCRYPT_GMAC_KEY_REFRESH_IV_OFFSET 44910
+typedef struct chiaki_key_state_t
+{
+ uint64_t prev;
+} ChiakiKeyState;
+
typedef struct chiaki_gkcrypt_t {
uint8_t index;
uint8_t *key_buf; // circular buffer of the ctr mode key stream
size_t key_buf_size;
size_t key_buf_populated; // size of key_buf that is already populated (on startup)
- size_t key_buf_key_pos_min; // minimal key pos currently in key_buf
+ uint64_t key_buf_key_pos_min; // minimal key pos currently in key_buf
size_t key_buf_start_offset; // offset in key_buf of the minimal key pos
- size_t last_key_pos; // last key pos that has been requested
+ uint64_t last_key_pos; // last key pos that has been requested
bool key_buf_thread_stop;
ChiakiMutex key_buf_mutex;
ChiakiCond key_buf_cond;
@@ -65,14 +55,14 @@ struct chiaki_session_t;
CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_init(ChiakiGKCrypt *gkcrypt, ChiakiLog *log, size_t key_buf_chunks, uint8_t index, const uint8_t *handshake_key, const uint8_t *ecdh_secret);
CHIAKI_EXPORT void chiaki_gkcrypt_fini(ChiakiGKCrypt *gkcrypt);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gen_key_stream(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_get_key_stream(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_decrypt(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size);
-static inline ChiakiErrorCode chiaki_gkcrypt_encrypt(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size) { return chiaki_gkcrypt_decrypt(gkcrypt, key_pos, buf, buf_size); }
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gen_key_stream(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_get_key_stream(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_decrypt(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size);
+static inline ChiakiErrorCode chiaki_gkcrypt_encrypt(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size) { return chiaki_gkcrypt_decrypt(gkcrypt, key_pos, buf, buf_size); }
CHIAKI_EXPORT void chiaki_gkcrypt_gen_gmac_key(uint64_t index, const uint8_t *key_base, const uint8_t *iv, uint8_t *key_out);
CHIAKI_EXPORT void chiaki_gkcrypt_gen_new_gmac_key(ChiakiGKCrypt *gkcrypt, uint64_t index);
CHIAKI_EXPORT void chiaki_gkcrypt_gen_tmp_gmac_key(ChiakiGKCrypt *gkcrypt, uint64_t index, uint8_t *key_out);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gmac(ChiakiGKCrypt *gkcrypt, size_t key_pos, const uint8_t *buf, size_t buf_size, uint8_t *gmac_out);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gmac(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, const uint8_t *buf, size_t buf_size, uint8_t *gmac_out);
static inline ChiakiGKCrypt *chiaki_gkcrypt_new(ChiakiLog *log, size_t key_buf_chunks, uint8_t index, const uint8_t *handshake_key, const uint8_t *ecdh_secret)
{
@@ -96,6 +86,18 @@ static inline void chiaki_gkcrypt_free(ChiakiGKCrypt *gkcrypt)
free(gkcrypt);
}
+CHIAKI_EXPORT void chiaki_key_state_init(ChiakiKeyState *state);
+
+/**
+ * @param commit whether to remember this key_pos to update the state. Should only be true after authentication to avoid DoS.
+ */
+CHIAKI_EXPORT uint64_t chiaki_key_state_request_pos(ChiakiKeyState *state, uint32_t low, bool commit);
+
+/**
+ * Update the internal state after knowing that this key_pos is authentic.
+ */
+CHIAKI_EXPORT void chiaki_key_state_commit(ChiakiKeyState *state, uint64_t prev);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/include/chiaki/http.h b/lib/include/chiaki/http.h
index 56c3a6f..f0ac444 100644
--- a/lib/include/chiaki/http.h
+++ b/lib/include/chiaki/http.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_HTTP_H
#define CHIAKI_HTTP_H
diff --git a/lib/include/chiaki/launchspec.h b/lib/include/chiaki/launchspec.h
index 7772dc3..770eb2a 100644
--- a/lib/include/chiaki/launchspec.h
+++ b/lib/include/chiaki/launchspec.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_LAUNCHSPEC_H
#define CHIAKI_LAUNCHSPEC_H
@@ -29,12 +14,14 @@ extern "C" {
typedef struct chiaki_launch_spec_t
{
+ ChiakiTarget target;
unsigned int mtu;
unsigned int rtt;
uint8_t *handshake_key;
unsigned int width;
unsigned int height;
unsigned int max_fps;
+ ChiakiCodec codec;
unsigned int bw_kbps_sent;
} ChiakiLaunchSpec;
diff --git a/lib/include/chiaki/log.h b/lib/include/chiaki/log.h
index f16b6ff..75cddd9 100644
--- a/lib/include/chiaki/log.h
+++ b/lib/include/chiaki/log.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_LOG_H
#define CHIAKI_LOG_H
@@ -65,6 +50,20 @@ CHIAKI_EXPORT void chiaki_log_hexdump_raw(ChiakiLog *log, ChiakiLogLevel level,
#define CHIAKI_LOGW(log, ...) do { chiaki_log((log), CHIAKI_LOG_WARNING, __VA_ARGS__); } while(0)
#define CHIAKI_LOGE(log, ...) do { chiaki_log((log), CHIAKI_LOG_ERROR, __VA_ARGS__); } while(0)
+typedef struct chiaki_log_sniffer_t
+{
+ ChiakiLog *forward_log; // The original log, where everything is forwarded
+ ChiakiLog sniff_log; // The log where others will log into
+ uint32_t sniff_level_mask;
+ char *buf; // always null-terminated
+ size_t buf_len; // strlen(buf)
+} ChiakiLogSniffer;
+
+CHIAKI_EXPORT void chiaki_log_sniffer_init(ChiakiLogSniffer *sniffer, uint32_t level_mask, ChiakiLog *forward_log);
+CHIAKI_EXPORT void chiaki_log_sniffer_fini(ChiakiLogSniffer *sniffer);
+static inline ChiakiLog *chiaki_log_sniffer_get_log(ChiakiLogSniffer *sniffer) { return &sniffer->sniff_log; }
+static inline const char *chiaki_log_sniffer_get_buffer(ChiakiLogSniffer *sniffer) { return sniffer->buf; }
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/include/chiaki/opusdecoder.h b/lib/include/chiaki/opusdecoder.h
index 4460b29..75ab8da 100644
--- a/lib/include/chiaki/opusdecoder.h
+++ b/lib/include/chiaki/opusdecoder.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_OPUSDECODER_H
#define CHIAKI_OPUSDECODER_H
diff --git a/lib/include/chiaki/orientation.h b/lib/include/chiaki/orientation.h
new file mode 100644
index 0000000..6dde62b
--- /dev/null
+++ b/lib/include/chiaki/orientation.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef CHIAKI_ORIENTATION_H
+#define CHIAKI_ORIENTATION_H
+
+#include "common.h"
+#include "controller.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Quaternion orientation from accelerometer and gyroscope
+ * using Madgwick's algorithm.
+ * See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
+ */
+typedef struct chiaki_orientation_t
+{
+ float x, y, z, w;
+} ChiakiOrientation;
+
+CHIAKI_EXPORT void chiaki_orientation_init(ChiakiOrientation *orient);
+CHIAKI_EXPORT void chiaki_orientation_update(ChiakiOrientation *orient,
+ float gx, float gy, float gz, float ax, float ay, float az, float beta, float time_step_sec);
+
+/**
+ * Extension of ChiakiOrientation, also tracking an absolute timestamp and the current gyro/accel state
+ */
+typedef struct chiaki_orientation_tracker_t
+{
+ float gyro_x, gyro_y, gyro_z;
+ float accel_x, accel_y, accel_z;
+ ChiakiOrientation orient;
+ uint32_t timestamp;
+ uint64_t sample_index;
+} ChiakiOrientationTracker;
+
+CHIAKI_EXPORT void chiaki_orientation_tracker_init(ChiakiOrientationTracker *tracker);
+CHIAKI_EXPORT void chiaki_orientation_tracker_update(ChiakiOrientationTracker *tracker,
+ float gx, float gy, float gz, float ax, float ay, float az, uint32_t timestamp_us);
+CHIAKI_EXPORT void chiaki_orientation_tracker_apply_to_controller_state(ChiakiOrientationTracker *tracker,
+ ChiakiControllerState *state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CHIAKI_ORIENTATION_H
diff --git a/lib/include/chiaki/packetstats.h b/lib/include/chiaki/packetstats.h
new file mode 100644
index 0000000..104c8a9
--- /dev/null
+++ b/lib/include/chiaki/packetstats.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef CHIAKI_PACKETSTATS_H
+#define CHIAKI_PACKETSTATS_H
+
+#include "thread.h"
+#include "seqnum.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct chiaki_packet_stats_t
+{
+ ChiakiMutex mutex;
+
+ // For generations of packets, i.e. where we know the number of expected packets per generation
+ uint64_t gen_received;
+ uint64_t gen_lost;
+
+ // For sequential packets, i.e. where packets are identified by a sequence number
+ ChiakiSeqNum16 seq_min; // sequence number that was max at the last reset
+ ChiakiSeqNum16 seq_max; // currently maximal sequence number
+ uint64_t seq_received; // total received packets since the last reset
+} ChiakiPacketStats;
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_packet_stats_init(ChiakiPacketStats *stats);
+CHIAKI_EXPORT void chiaki_packet_stats_fini(ChiakiPacketStats *stats);
+CHIAKI_EXPORT void chiaki_packet_stats_reset(ChiakiPacketStats *stats);
+CHIAKI_EXPORT void chiaki_packet_stats_push_generation(ChiakiPacketStats *stats, uint64_t received, uint64_t lost);
+CHIAKI_EXPORT void chiaki_packet_stats_push_seq(ChiakiPacketStats *stats, ChiakiSeqNum16 seq_num);
+CHIAKI_EXPORT void chiaki_packet_stats_get(ChiakiPacketStats *stats, bool reset, uint64_t *received, uint64_t *lost);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CHIAKI_PACKETSTATS_H
diff --git a/lib/include/chiaki/pidecoder.h b/lib/include/chiaki/pidecoder.h
new file mode 100644
index 0000000..af2b18b
--- /dev/null
+++ b/lib/include/chiaki/pidecoder.h
@@ -0,0 +1,36 @@
+#ifndef CHIAKI_PI_DECODER_H
+#define CHIAKI_PI_DECODER_H
+
+#include
+#include
+
+#include
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct chiaki_pi_decoder_t
+{
+ ChiakiLog *log;
+ TUNNEL_T tunnel[2];
+ COMPONENT_T *components[3];
+ ILCLIENT_T *client;
+ COMPONENT_T *video_decode;
+ COMPONENT_T *video_render;
+ bool port_settings_changed;
+ bool first_packet;
+} ChiakiPiDecoder;
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_pi_decoder_init(ChiakiPiDecoder *decoder, ChiakiLog *log);
+CHIAKI_EXPORT void chiaki_pi_decoder_fini(ChiakiPiDecoder *decoder);
+CHIAKI_EXPORT void chiaki_pi_decoder_set_params(ChiakiPiDecoder *decoder, int x, int y, int w, int h, bool visible);
+CHIAKI_EXPORT bool chiaki_pi_decoder_video_sample_cb(uint8_t *buf, size_t buf_size, void *user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CHIAKI_PI_DECODER_H
diff --git a/lib/include/chiaki/random.h b/lib/include/chiaki/random.h
index 33bb1f2..9898ec5 100644
--- a/lib/include/chiaki/random.h
+++ b/lib/include/chiaki/random.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_RANDOM_H
#define CHIAKI_RANDOM_H
diff --git a/lib/include/chiaki/regist.h b/lib/include/chiaki/regist.h
index 6140967..9ad1efc 100644
--- a/lib/include/chiaki/regist.h
+++ b/lib/include/chiaki/regist.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_REGIST_H
#define CHIAKI_REGIST_H
@@ -33,6 +18,7 @@ extern "C" {
typedef struct chiaki_regist_info_t
{
+ ChiakiTarget target;
const char *host;
bool broadcast;
@@ -51,12 +37,13 @@ typedef struct chiaki_regist_info_t
typedef struct chiaki_registered_host_t
{
+ ChiakiTarget target;
char ap_ssid[0x30];
char ap_bssid[0x20];
char ap_key[0x50];
char ap_name[0x20];
- uint8_t ps4_mac[6];
- char ps4_nickname[0x20];
+ uint8_t server_mac[6];
+ char server_nickname[0x20];
char rp_regist_key[CHIAKI_SESSION_AUTH_SIZE]; // must be completely filled (pad with \0)
uint32_t rp_key_type;
uint8_t rp_key[0x10];
@@ -94,7 +81,7 @@ CHIAKI_EXPORT void chiaki_regist_stop(ChiakiRegist *regist);
/**
* @param psn_account_id must be exactly of size CHIAKI_PSN_ACCOUNT_ID_SIZE
*/
-CHIAKI_EXPORT ChiakiErrorCode chiaki_regist_request_payload_format(uint8_t *buf, size_t *buf_size, ChiakiRPCrypt *crypt, const char *psn_online_id, const uint8_t *psn_account_id);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_regist_request_payload_format(ChiakiTarget target, const uint8_t *ambassador, uint8_t *buf, size_t *buf_size, ChiakiRPCrypt *crypt, const char *psn_online_id, const uint8_t *psn_account_id, uint32_t pin);
#ifdef __cplusplus
}
diff --git a/lib/include/chiaki/reorderqueue.h b/lib/include/chiaki/reorderqueue.h
index 54e6472..e972d91 100644
--- a/lib/include/chiaki/reorderqueue.h
+++ b/lib/include/chiaki/reorderqueue.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_REORDERQUEUE_H
#define CHIAKI_REORDERQUEUE_H
diff --git a/lib/include/chiaki/rpcrypt.h b/lib/include/chiaki/rpcrypt.h
index 12d0705..7df16a5 100644
--- a/lib/include/chiaki/rpcrypt.h
+++ b/lib/include/chiaki/rpcrypt.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_RPCRYPT_H
#define CHIAKI_RPCRYPT_H
@@ -31,15 +16,18 @@ extern "C" {
typedef struct chiaki_rpcrypt_t
{
+ ChiakiTarget target;
uint8_t bright[CHIAKI_RPCRYPT_KEY_SIZE];
uint8_t ambassador[CHIAKI_RPCRYPT_KEY_SIZE];
} ChiakiRPCrypt;
-CHIAKI_EXPORT void chiaki_rpcrypt_bright_ambassador(uint8_t *bright, uint8_t *ambassador, const uint8_t *nonce, const uint8_t *morning);
-CHIAKI_EXPORT void chiaki_rpcrypt_aeropause(uint8_t *aeropause, const uint8_t *ambassador);
+CHIAKI_EXPORT void chiaki_rpcrypt_bright_ambassador(ChiakiTarget target, uint8_t *bright, uint8_t *ambassador, const uint8_t *nonce, const uint8_t *morning);
+CHIAKI_EXPORT void chiaki_rpcrypt_aeropause_ps4_pre10(uint8_t *aeropause, const uint8_t *ambassador);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_aeropause(ChiakiTarget target, size_t key_1_off, uint8_t *aeropause, const uint8_t *ambassador);
-CHIAKI_EXPORT void chiaki_rpcrypt_init_auth(ChiakiRPCrypt *rpcrypt, const uint8_t *nonce, const uint8_t *morning);
-CHIAKI_EXPORT void chiaki_rpcrypt_init_regist(ChiakiRPCrypt *rpcrypt, const uint8_t *ambassador, uint32_t pin);
+CHIAKI_EXPORT void chiaki_rpcrypt_init_auth(ChiakiRPCrypt *rpcrypt, ChiakiTarget target, const uint8_t *nonce, const uint8_t *morning);
+CHIAKI_EXPORT void chiaki_rpcrypt_init_regist_ps4_pre10(ChiakiRPCrypt *rpcrypt, const uint8_t *ambassador, uint32_t pin);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_init_regist(ChiakiRPCrypt *rpcrypt, ChiakiTarget target, const uint8_t *ambassador, size_t key_0_off, uint32_t pin);
CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_generate_iv(ChiakiRPCrypt *rpcrypt, uint8_t *iv, uint64_t counter);
CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_encrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz);
CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_decrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz);
diff --git a/lib/include/chiaki/senkusha.h b/lib/include/chiaki/senkusha.h
index de1185b..4e74ed2 100644
--- a/lib/include/chiaki/senkusha.h
+++ b/lib/include/chiaki/senkusha.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SENKUSHA_H
#define CHIAKI_SENKUSHA_H
diff --git a/lib/include/chiaki/seqnum.h b/lib/include/chiaki/seqnum.h
index 77825b7..703a681 100644
--- a/lib/include/chiaki/seqnum.h
+++ b/lib/include/chiaki/seqnum.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SEQNUM_H
#define CHIAKI_SEQNUM_H
diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h
index a18ccfb..5c355ad 100644
--- a/lib/include/chiaki/session.h
+++ b/lib/include/chiaki/session.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SESSION_H
#define CHIAKI_SESSION_H
@@ -27,8 +12,6 @@
#include "takion.h"
#include "ecdh.h"
#include "audio.h"
-#include "audioreceiver.h"
-#include "videoreceiver.h"
#include "controller.h"
#include "stoppipe.h"
@@ -43,22 +26,16 @@ extern "C" {
#define CHIAKI_RP_APPLICATION_REASON_IN_USE 0x80108b10
#define CHIAKI_RP_APPLICATION_REASON_CRASH 0x80108b15
#define CHIAKI_RP_APPLICATION_REASON_RP_VERSION 0x80108b11
-// unknown: 0x80108bff
+#define CHIAKI_RP_APPLICATION_REASON_UNKNOWN 0x80108bff
CHIAKI_EXPORT const char *chiaki_rp_application_reason_string(uint32_t reason);
-typedef enum {
- CHIAKI_RP_VERSION_UNKNOWN = 0,
- CHIAKI_RP_VERSION_8_0 = 800,
- CHIAKI_RP_VERSION_9_0 = 900
-} ChiakiRpVersion;
-
/**
* @return RP-Version string or NULL
*/
-CHIAKI_EXPORT const char *chiaki_rp_version_string(ChiakiRpVersion version);
+CHIAKI_EXPORT const char *chiaki_rp_version_string(ChiakiTarget target);
-CHIAKI_EXPORT ChiakiRpVersion chiaki_rp_version_parse(const char *rp_version_str);
+CHIAKI_EXPORT ChiakiTarget chiaki_rp_version_parse(const char *rp_version_str, bool is_ps5);
#define CHIAKI_RP_DID_SIZE 32
@@ -71,6 +48,7 @@ typedef struct chiaki_connect_video_profile_t
unsigned int height;
unsigned int max_fps;
unsigned int bitrate;
+ ChiakiCodec codec;
} ChiakiConnectVideoProfile;
typedef enum {
@@ -93,10 +71,14 @@ CHIAKI_EXPORT void chiaki_connect_video_profile_preset(ChiakiConnectVideoProfile
typedef struct chiaki_connect_info_t
{
+ bool ps5;
const char *host; // null terminated
char regist_key[CHIAKI_SESSION_AUTH_SIZE]; // must be completely filled (pad with \0)
uint8_t morning[0x10];
ChiakiConnectVideoProfile video_profile;
+ bool video_profile_auto_downgrade; // Downgrade video_profile if server does not seem to support it.
+ bool enable_keyboard;
+ bool enable_dualsense;
} ChiakiConnectInfo;
@@ -112,27 +94,57 @@ typedef enum {
CHIAKI_QUIT_REASON_CTRL_CONNECT_FAILED,
CHIAKI_QUIT_REASON_CTRL_CONNECTION_REFUSED,
CHIAKI_QUIT_REASON_STREAM_CONNECTION_UNKNOWN,
- CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED
+ CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED,
+ CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN, // like REMOTE_DISCONNECTED, but because the server shut down
} ChiakiQuitReason;
CHIAKI_EXPORT const char *chiaki_quit_reason_string(ChiakiQuitReason reason);
+static inline bool chiaki_quit_reason_is_error(ChiakiQuitReason reason)
+{
+ return reason != CHIAKI_QUIT_REASON_STOPPED && reason != CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN;
+}
+
typedef struct chiaki_quit_event_t
{
ChiakiQuitReason reason;
const char *reason_str;
} ChiakiQuitEvent;
+typedef struct chiaki_keyboard_event_t
+{
+ const char *text_str;
+} ChiakiKeyboardEvent;
+
typedef struct chiaki_audio_stream_info_event_t
{
ChiakiAudioHeader audio_header;
} ChiakiAudioStreamInfoEvent;
+typedef struct chiaki_rumble_event_t
+{
+ uint8_t unknown;
+ uint8_t left; // low-frequency
+ uint8_t right; // high-frequency
+} ChiakiRumbleEvent;
+
+typedef struct chiaki_trigger_effects_event_t
+{
+ uint8_t type_left;
+ uint8_t type_right;
+ uint8_t left[10];
+ uint8_t right[10];
+} ChiakiTriggerEffectsEvent;
typedef enum {
CHIAKI_EVENT_CONNECTED,
CHIAKI_EVENT_LOGIN_PIN_REQUEST,
- CHIAKI_EVENT_QUIT
+ CHIAKI_EVENT_KEYBOARD_OPEN,
+ CHIAKI_EVENT_KEYBOARD_TEXT_CHANGE,
+ CHIAKI_EVENT_KEYBOARD_REMOTE_CLOSE,
+ CHIAKI_EVENT_RUMBLE,
+ CHIAKI_EVENT_QUIT,
+ CHIAKI_EVENT_TRIGGER_EFFECTS,
} ChiakiEventType;
typedef struct chiaki_event_t
@@ -141,6 +153,9 @@ typedef struct chiaki_event_t
union
{
ChiakiQuitEvent quit;
+ ChiakiKeyboardEvent keyboard;
+ ChiakiRumbleEvent rumble;
+ ChiakiTriggerEffectsEvent trigger_effects;
struct
{
bool pin_incorrect; // false on first request, true if the pin entered before was incorrect
@@ -162,16 +177,20 @@ typedef struct chiaki_session_t
{
struct
{
+ bool ps5;
struct addrinfo *host_addrinfos;
struct addrinfo *host_addrinfo_selected;
- char hostname[128];
+ char hostname[256];
char regist_key[CHIAKI_RPCRYPT_KEY_SIZE];
uint8_t morning[CHIAKI_RPCRYPT_KEY_SIZE];
uint8_t did[CHIAKI_RP_DID_SIZE];
ChiakiConnectVideoProfile video_profile;
+ bool video_profile_auto_downgrade;
+ bool enable_keyboard;
+ bool enable_dualsense;
} connect_info;
- ChiakiRpVersion rp_version;
+ ChiakiTarget target;
uint8_t nonce[CHIAKI_RPCRYPT_KEY_SIZE];
ChiakiRPCrypt rpcrypt;
@@ -190,6 +209,7 @@ typedef struct chiaki_session_t
ChiakiVideoSampleCallback video_sample_cb;
void *video_sample_cb_user;
ChiakiAudioSink audio_sink;
+ ChiakiAudioSink haptics_sink;
ChiakiThread session_thread;
@@ -209,8 +229,6 @@ typedef struct chiaki_session_t
ChiakiLog *log;
ChiakiStreamConnection stream_connection;
- ChiakiAudioReceiver *audio_receiver;
- ChiakiVideoReceiver *video_receiver;
ChiakiControllerState controller_state;
} ChiakiSession;
@@ -222,6 +240,10 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_stop(ChiakiSession *session);
CHIAKI_EXPORT ChiakiErrorCode chiaki_session_join(ChiakiSession *session);
CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_controller_state(ChiakiSession *session, ChiakiControllerState *state);
CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, const uint8_t *pin, size_t pin_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_goto_bed(ChiakiSession *session);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_keyboard_set_text(ChiakiSession *session, const char *text);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_keyboard_reject(ChiakiSession *session);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_keyboard_accept(ChiakiSession *session);
static inline void chiaki_session_set_event_cb(ChiakiSession *session, ChiakiEventCallback cb, void *user)
{
@@ -243,6 +265,14 @@ static inline void chiaki_session_set_audio_sink(ChiakiSession *session, ChiakiA
session->audio_sink = *sink;
}
+/**
+ * @param sink contents are copied
+ */
+static inline void chiaki_session_set_haptics_sink(ChiakiSession *session, ChiakiAudioSink *sink)
+{
+ session->haptics_sink = *sink;
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/include/chiaki/sock.h b/lib/include/chiaki/sock.h
index c675b8c..9ddae9c 100644
--- a/lib/include/chiaki/sock.h
+++ b/lib/include/chiaki/sock.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_SOCK_H
#define CHIAKI_SOCK_H
diff --git a/lib/include/chiaki/stoppipe.h b/lib/include/chiaki/stoppipe.h
index 229995e..3c585b2 100644
--- a/lib/include/chiaki/stoppipe.h
+++ b/lib/include/chiaki/stoppipe.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_STOPPIPE_H
#define CHIAKI_STOPPIPE_H
@@ -26,6 +11,8 @@
#ifdef _WIN32
#include
+#else
+#include
#endif
#ifdef __cplusplus
@@ -36,6 +23,15 @@ typedef struct chiaki_stop_pipe_t
{
#ifdef _WIN32
WSAEVENT event;
+#elif defined(__SWITCH__)
+ // due to a lack pipe/event/socketpair
+ // on switch env, we use a physical socket
+ // to send/trigger the cancel signal
+ struct sockaddr_in addr;
+ // local stop socket file descriptor
+ // this fd is audited by 'select' as
+ // fd_set *readfds
+ int fd;
#else
int fds[2];
#endif
diff --git a/lib/include/chiaki/streamconnection.h b/lib/include/chiaki/streamconnection.h
index c8f2bea..90578c6 100644
--- a/lib/include/chiaki/streamconnection.h
+++ b/lib/include/chiaki/streamconnection.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_STREAMCONNECTION_H
#define CHIAKI_STREAMCONNECTION_H
@@ -23,6 +8,9 @@
#include "log.h"
#include "ecdh.h"
#include "gkcrypt.h"
+#include "audioreceiver.h"
+#include "videoreceiver.h"
+#include "congestioncontrol.h"
#include
@@ -41,6 +29,11 @@ typedef struct chiaki_stream_connection_t
ChiakiGKCrypt *gkcrypt_local;
ChiakiGKCrypt *gkcrypt_remote;
+ ChiakiPacketStats packet_stats;
+ ChiakiAudioReceiver *audio_receiver;
+ ChiakiVideoReceiver *video_receiver;
+ ChiakiAudioReceiver *haptics_receiver;
+
ChiakiFeedbackSender feedback_sender;
/**
* whether feedback_sender is initialized
diff --git a/lib/include/chiaki/takion.h b/lib/include/chiaki/takion.h
index b242769..15d62e5 100644
--- a/lib/include/chiaki/takion.h
+++ b/lib/include/chiaki/takion.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_TAKION_H
#define CHIAKI_TAKION_H
@@ -39,11 +24,11 @@
extern "C" {
#endif
-typedef uint32_t ChiakiTakionPacketKeyPos;
-
typedef enum chiaki_takion_message_data_type_t {
CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF = 0,
- CHIAKI_TAKION_MESSAGE_DATA_TYPE_9 = 9
+ CHIAKI_TAKION_MESSAGE_DATA_TYPE_RUMBLE = 7,
+ CHIAKI_TAKION_MESSAGE_DATA_TYPE_9 = 9,
+ CHIAKI_TAKION_MESSAGE_DATA_TYPE_TRIGGER_EFFECTS = 11,
} ChiakiTakionMessageDataType;
typedef struct chiaki_takion_av_packet_t
@@ -52,6 +37,7 @@ typedef struct chiaki_takion_av_packet_t
ChiakiSeqNum16 frame_index;
bool uses_nalu_info_structs;
bool is_video;
+ bool is_haptics;
ChiakiSeqNum16 unit_index;
uint16_t units_in_frame_total; // source + units_in_frame_fec
uint16_t units_in_frame_fec;
@@ -60,7 +46,7 @@ typedef struct chiaki_takion_av_packet_t
uint8_t adaptive_stream_index;
uint8_t byte_at_0x2c;
- uint32_t key_pos;
+ uint64_t key_pos;
uint8_t *data; // not owned
size_t data_size;
@@ -70,13 +56,13 @@ static inline uint8_t chiaki_takion_av_packet_audio_unit_size(ChiakiTakionAVPack
static inline uint8_t chiaki_takion_av_packet_audio_source_units_count(ChiakiTakionAVPacket *packet) { return packet->units_in_frame_fec & 0xf; }
static inline uint8_t chiaki_takion_av_packet_audio_fec_units_count(ChiakiTakionAVPacket *packet) { return (packet->units_in_frame_fec >> 4) & 0xf; }
-typedef ChiakiErrorCode (*ChiakiTakionAVPacketParse)(ChiakiTakionAVPacket *packet, uint8_t *buf, size_t buf_size);
+typedef ChiakiErrorCode (*ChiakiTakionAVPacketParse)(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size);
typedef struct chiaki_takion_congestion_packet_t
{
uint16_t word_0;
- uint16_t word_1;
- uint16_t word_2;
+ uint16_t received;
+ uint16_t lost;
} ChiakiTakionCongestionPacket;
@@ -120,6 +106,7 @@ typedef struct chiaki_takion_connect_info_t
ChiakiTakionCallback cb;
void *cb_user;
bool enable_crypt;
+ bool enable_dualsense;
uint8_t protocol_version;
} ChiakiTakionConnectInfo;
@@ -127,6 +114,7 @@ typedef struct chiaki_takion_connect_info_t
typedef struct chiaki_takion_t
{
ChiakiLog *log;
+ uint8_t version;
/**
* Whether encryption should be used.
@@ -148,7 +136,7 @@ typedef struct chiaki_takion_t
size_t postponed_packets_count;
ChiakiGKCrypt *gkcrypt_local; // if NULL (default), no gmac is calculated and nothing is encrypted
- size_t key_pos_local;
+ uint64_t key_pos_local;
ChiakiMutex gkcrypt_local_mutex;
ChiakiGKCrypt *gkcrypt_remote; // if NULL (default), remote gmacs are IGNORED (!) and everything is expected to be unencrypted
@@ -173,6 +161,10 @@ typedef struct chiaki_takion_t
uint32_t a_rwnd;
ChiakiTakionAVPacketParse av_packet_parse;
+
+ ChiakiKeyState key_state;
+
+ bool enable_dualsense;
} ChiakiTakion;
@@ -182,18 +174,21 @@ CHIAKI_EXPORT void chiaki_takion_close(ChiakiTakion *takion);
/**
* Must be called from within the Takion thread, i.e. inside the callback!
*/
-static inline void chiaki_takion_set_crypt(ChiakiTakion *takion, ChiakiGKCrypt *gkcrypt_local, ChiakiGKCrypt *gkcrypt_remote) {
+static inline void chiaki_takion_set_crypt(ChiakiTakion *takion, ChiakiGKCrypt *gkcrypt_local, ChiakiGKCrypt *gkcrypt_remote)
+{
takion->gkcrypt_local = gkcrypt_local;
takion->gkcrypt_remote = gkcrypt_remote;
}
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_packet_mac(ChiakiGKCrypt *crypt, uint8_t *buf, size_t buf_size, uint64_t key_pos, uint8_t *mac_out, uint8_t *mac_old_out);
+
/**
* Get a new key pos and advance by data_size.
*
* Thread-safe while Takion is running.
* @param key_pos pointer to write the new key pos to. will be 0 if encryption is disabled. Contents undefined on failure.
*/
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_crypt_advance_key_pos(ChiakiTakion *takion, size_t data_size, size_t *key_pos);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_crypt_advance_key_pos(ChiakiTakion *takion, size_t data_size, uint64_t *key_pos);
/**
* Send a datagram directly on the socket.
@@ -208,7 +203,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_raw(ChiakiTakion *takion, const
*
* If encryption is disabled, the MAC will be set to 0.
*/
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send(ChiakiTakion *takion, uint8_t *buf, size_t buf_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send(ChiakiTakion *takion, uint8_t *buf, size_t buf_size, uint64_t key_pos);
/**
* Thread-safe while Takion is running.
@@ -235,7 +230,12 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_feedback_history(ChiakiTakion *
#define CHIAKI_TAKION_V9_AV_HEADER_SIZE_VIDEO 0x17
#define CHIAKI_TAKION_V9_AV_HEADER_SIZE_AUDIO 0x12
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPacket *packet, uint8_t *buf, size_t buf_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size);
+
+#define CHIAKI_TAKION_V12_AV_HEADER_SIZE_VIDEO 0x17
+#define CHIAKI_TAKION_V12_AV_HEADER_SIZE_AUDIO 0x13
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v12_av_packet_parse(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size);
#define CHIAKI_TAKION_V7_AV_HEADER_SIZE_BASE 0x12
#define CHIAKI_TAKION_V7_AV_HEADER_SIZE_VIDEO_ADD 0x3
@@ -243,7 +243,11 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPac
CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_format_header(uint8_t *buf, size_t buf_size, size_t *header_size_out, ChiakiTakionAVPacket *packet);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_parse(ChiakiTakionAVPacket *packet, uint8_t *buf, size_t buf_size);
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_parse(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size);
+
+#define CHIAKI_TAKION_CONGESTION_PACKET_SIZE 0xf
+
+CHIAKI_EXPORT void chiaki_takion_format_congestion(uint8_t *buf, ChiakiTakionCongestionPacket *packet, uint64_t key_pos);
#ifdef __cplusplus
}
diff --git a/lib/include/chiaki/takionsendbuffer.h b/lib/include/chiaki/takionsendbuffer.h
index 3ecff3a..8ddecc0 100644
--- a/lib/include/chiaki/takionsendbuffer.h
+++ b/lib/include/chiaki/takionsendbuffer.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_TAKIONSENDBUFFER_H
#define CHIAKI_TAKIONSENDBUFFER_H
diff --git a/lib/include/chiaki/thread.h b/lib/include/chiaki/thread.h
index f98488c..621a1ce 100644
--- a/lib/include/chiaki/thread.h
+++ b/lib/include/chiaki/thread.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_THREAD_H
#define CHIAKI_THREAD_H
diff --git a/lib/include/chiaki/time.h b/lib/include/chiaki/time.h
index ef39af8..6e31d07 100644
--- a/lib/include/chiaki/time.h
+++ b/lib/include/chiaki/time.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_TIME_H
#define CHIAKI_TIME_H
diff --git a/lib/include/chiaki/video.h b/lib/include/chiaki/video.h
index 5555815..3ff3ee8 100644
--- a/lib/include/chiaki/video.h
+++ b/lib/include/chiaki/video.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_VIDEO_H
#define CHIAKI_VIDEO_H
diff --git a/lib/include/chiaki/videoreceiver.h b/lib/include/chiaki/videoreceiver.h
index 0ce8f0c..4a814c2 100644
--- a/lib/include/chiaki/videoreceiver.h
+++ b/lib/include/chiaki/videoreceiver.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_VIDEORECEIVER_H
#define CHIAKI_VIDEORECEIVER_H
@@ -42,9 +27,10 @@ typedef struct chiaki_video_receiver_t
int32_t frame_index_prev; // last frame that has been at least partially decoded
int32_t frame_index_prev_complete; // last frame that has been completely decoded
ChiakiFrameProcessor frame_processor;
+ ChiakiPacketStats *packet_stats;
} ChiakiVideoReceiver;
-CHIAKI_EXPORT void chiaki_video_receiver_init(ChiakiVideoReceiver *video_receiver, struct chiaki_session_t *session);
+CHIAKI_EXPORT void chiaki_video_receiver_init(ChiakiVideoReceiver *video_receiver, struct chiaki_session_t *session, ChiakiPacketStats *packet_stats);
CHIAKI_EXPORT void chiaki_video_receiver_fini(ChiakiVideoReceiver *video_receiver);
/**
@@ -58,12 +44,12 @@ CHIAKI_EXPORT void chiaki_video_receiver_stream_info(ChiakiVideoReceiver *video_
CHIAKI_EXPORT void chiaki_video_receiver_av_packet(ChiakiVideoReceiver *video_receiver, ChiakiTakionAVPacket *packet);
-static inline ChiakiVideoReceiver *chiaki_video_receiver_new(struct chiaki_session_t *session)
+static inline ChiakiVideoReceiver *chiaki_video_receiver_new(struct chiaki_session_t *session, ChiakiPacketStats *packet_stats)
{
ChiakiVideoReceiver *video_receiver = CHIAKI_NEW(ChiakiVideoReceiver);
if(!video_receiver)
return NULL;
- chiaki_video_receiver_init(video_receiver, session);
+ chiaki_video_receiver_init(video_receiver, session, packet_stats);
return video_receiver;
}
diff --git a/lib/protobuf/CMakeLists.txt b/lib/protobuf/CMakeLists.txt
index 6efa342..43fde68 100644
--- a/lib/protobuf/CMakeLists.txt
+++ b/lib/protobuf/CMakeLists.txt
@@ -11,8 +11,16 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/takion.pb"
set(SOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/takion.pb.c")
set(HEADER_FILES "${CMAKE_CURRENT_BINARY_DIR}/takion.pb.h")
+if(UNIX AND IS_ABSOLUTE "${PROTOC}")
+ # make sure protoc is in PATH when invoking the generator below, which needs it.
+ get_filename_component(PROTOC_PATH "${PROTOC}" DIRECTORY)
+ set(GEN_PREFIX "${CMAKE_COMMAND}" -E env "PATH=${PROTOC_PATH}:$ENV{PATH}")
+else()
+ set(GEN_PREFIX "")
+endif()
+
add_custom_command(OUTPUT ${SOURCE_FILES} ${HEADER_FILES}
- COMMAND "${PYTHON_EXECUTABLE}" "${NANOPB_GENERATOR_PY}" "${CMAKE_CURRENT_BINARY_DIR}/takion.pb"
+ COMMAND ${GEN_PREFIX} "${PYTHON_EXECUTABLE}" "${NANOPB_GENERATOR_PY}" "${CMAKE_CURRENT_BINARY_DIR}/takion.pb"
MAIN_DEPENDENCY "${CMAKE_CURRENT_BINARY_DIR}/takion.pb")
set(CHIAKI_LIB_PROTO_SOURCE_FILES "${SOURCE_FILES}" PARENT_SCOPE)
diff --git a/lib/protobuf/takion.proto b/lib/protobuf/takion.proto
index 9896e4f..ceef0f1 100644
--- a/lib/protobuf/takion.proto
+++ b/lib/protobuf/takion.proto
@@ -35,6 +35,9 @@ message TakionMessage {
PERIODICTIMESTAMP = 27;
SERVERSETTINGS = 28;
DIRECTMESSAGE = 29;
+ MICCONNECTION = 30;
+ TAKIONPROTOCOLREQUEST = 31;
+ TAKIONPROTOCOLREQUESTACK = 32;
}
optional BigPayload big_payload = 2;
@@ -64,6 +67,9 @@ message TakionMessage {
optional PeriodicTimestampPayload periodic_timestamp_payload = 27;
optional ServerSettingsPayload server_settings_payload = 28;
optional DirectMessagePayload direct_message_payload = 29;
+ optional MicConnectionPayload mic_connection_payload = 30;
+ optional TakionProtocolRequestPayload takion_protocol_request = 31;
+ optional TakionProtocolRequestAckPayload takion_protocol_request_ack = 32;
}
message EventCode {
@@ -249,6 +255,12 @@ message ResolutionPayload {
required bytes video_header = 3;
}
+message AudioChannelPayload {
+ required uint32 audio_channel_type = 1;
+ required bytes audio_header = 2;
+ optional bool is_raw_pcm = 3;
+}
+
message StreamInfoPayload {
repeated ResolutionPayload resolution = 1;
required bytes audio_header = 2;
@@ -256,6 +268,7 @@ message StreamInfoPayload {
optional uint32 afk_timeout = 4;
optional uint32 afk_timeout_disconnect = 5;
optional uint32 congestion_control_interval = 6;
+ repeated AudioChannelPayload audio_channel = 7;
}
message XmbCommandPayload {
@@ -274,6 +287,8 @@ message ConnectionQualityPayload {
optional uint32 upstream_bitrate = 2;
optional float upstream_loss = 3;
optional bool disable_upstream_audio = 4;
+ optional double rtt = 5;
+ optional uint64 loss = 6;
}
message PlayTimeLeftPayload {
@@ -296,6 +311,9 @@ message ControllerConnectionPayload {
DUALSHOCK4 = 2;
VITA = 3;
XINPUT = 4;
+ MOBILE = 5;
+ DUALSENSE = 6;
+ VR2SENSE = 7;
}
}
@@ -338,6 +356,7 @@ message DeepLinkPayload {
GAME_ALERT = 2;
SYSTEM_SERVICE_STATUS = 3;
DEBUG_VSH_MENU = 4;
+ RAW = 5;
}
optional uint32 request_id = 2;
@@ -345,15 +364,30 @@ message DeepLinkPayload {
optional string invitation_id = 4;
optional string session_id = 5;
optional string item_id = 6;
- optional string is_system_ui_qverlaid = 7;
+ optional string is_system_ui_overlaid = 7;
optional uint32 result = 8;
optional bool should_show = 9;
+ optional string raw_data = 10;
+}
+
+message MicInfoPayload {
+ required MicInfoType mic_info_type = 1;
+ enum MicInfoType {
+ MIC_CONNECT = 0;
+ MIC_MUTE = 1;
+ }
+
+ optional uint32 controller_id = 2;
+ optional bool connected = 3;
+ optional bool muted = 4;
+ optional bool result = 5;
}
message DirectMessagePayload {
required DirectMessageType direct_message_type = 1;
enum DirectMessageType {
DEEPLINK = 0;
+ MICINFO = 1;
}
required Destination destination = 2;
@@ -365,7 +399,19 @@ message DirectMessagePayload {
optional bytes data = 3;
}
+message MicConnectionPayload {
+ required int32 controller_id = 1;
+ required bool connected = 2;
+ optional bool result = 3;
+}
+message TakionProtocolRequestPayload {
+ repeated uint32 supported_takion_versions = 1;
+}
+
+message TakionProtocolRequestAckPayload {
+ optional uint32 takion_protocol_version = 1;
+}
message SenkushaPayload {
required Command command = 1;
@@ -412,4 +458,4 @@ message SenkushaClientMtuCommand {
required uint32 mtu_req = 2;
required bool state = 3;
optional uint32 mtu_down = 4;
-}
\ No newline at end of file
+}
diff --git a/lib/src/audio.c b/lib/src/audio.c
index c7a3512..c7f8dce 100644
--- a/lib/src/audio.c
+++ b/lib/src/audio.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -23,6 +8,9 @@
#include
#endif
+#ifdef __SWITCH__
+#include
+#endif
void chiaki_audio_header_load(ChiakiAudioHeader *audio_header, const uint8_t *buf)
{
diff --git a/lib/src/audioreceiver.c b/lib/src/audioreceiver.c
index 54f52cd..744d807 100644
--- a/lib/src/audioreceiver.c
+++ b/lib/src/audioreceiver.c
@@ -1,31 +1,17 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
#include
-static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, uint8_t *buf, size_t buf_size);
+static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, bool is_haptics, uint8_t *buf, size_t buf_size);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *audio_receiver, ChiakiSession *session)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *audio_receiver, ChiakiSession *session, ChiakiPacketStats *packet_stats)
{
audio_receiver->session = session;
audio_receiver->log = session->log;
+ audio_receiver->packet_stats = packet_stats;
audio_receiver->frame_index_prev = 0;
audio_receiver->frame_index_startup = true;
@@ -37,7 +23,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *au
return CHIAKI_ERR_SUCCESS;
}
-
CHIAKI_EXPORT void chiaki_audio_receiver_fini(ChiakiAudioReceiver *audio_receiver)
{
#ifdef CHIAKI_LIB_ENABLE_OPUS
@@ -46,7 +31,6 @@ CHIAKI_EXPORT void chiaki_audio_receiver_fini(ChiakiAudioReceiver *audio_receive
chiaki_mutex_fini(&audio_receiver->mutex);
}
-
CHIAKI_EXPORT void chiaki_audio_receiver_stream_info(ChiakiAudioReceiver *audio_receiver, ChiakiAudioHeader *audio_header)
{
chiaki_mutex_lock(&audio_receiver->mutex);
@@ -92,14 +76,16 @@ CHIAKI_EXPORT void chiaki_audio_receiver_av_packet(ChiakiAudioReceiver *audio_re
if(packet->data_size != (size_t)unit_size * (size_t)packet->units_in_frame_total)
{
- CHIAKI_LOGE(audio_receiver->log, "Audio AV Packet size mismatch");
+ CHIAKI_LOGE(audio_receiver->log, "Audio AV Packet size mismatch %#llx vs %#llx",
+ (unsigned long long)packet->data_size,
+ (unsigned long long)(unit_size * packet->units_in_frame_total));
return;
}
if(packet->frame_index > (1 << 15))
audio_receiver->frame_index_startup = false;
- for(size_t i=0; iframe_index - fec_units_count + fec_index;
}
- chiaki_audio_receiver_frame(audio_receiver->session->audio_receiver, frame_index, packet->data + unit_size * i, unit_size);
+ chiaki_audio_receiver_frame(audio_receiver, frame_index, packet->is_haptics, packet->data + unit_size * i, unit_size);
}
+
+ if(audio_receiver->packet_stats)
+ chiaki_packet_stats_push_seq(audio_receiver->packet_stats, packet->frame_index);
}
-static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, uint8_t *buf, size_t buf_size)
+static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, bool is_haptics, uint8_t *buf, size_t buf_size)
{
chiaki_mutex_lock(&audio_receiver->mutex);
@@ -128,9 +117,11 @@ static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, Chi
goto beach;
audio_receiver->frame_index_prev = frame_index;
- if(audio_receiver->session->audio_sink.frame_cb)
+ if(is_haptics && audio_receiver->session->haptics_sink.frame_cb)
+ audio_receiver->session->haptics_sink.frame_cb(buf, buf_size, audio_receiver->session->haptics_sink.user);
+ else if(!is_haptics && audio_receiver->session->audio_sink.frame_cb)
audio_receiver->session->audio_sink.frame_cb(buf, buf_size, audio_receiver->session->audio_sink.user);
beach:
chiaki_mutex_unlock(&audio_receiver->mutex);
-}
\ No newline at end of file
+}
diff --git a/lib/src/base64.c b/lib/src/base64.c
index 8e80732..9d0fecd 100644
--- a/lib/src/base64.c
+++ b/lib/src/base64.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -22,7 +7,6 @@
// Implementations taken from
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_encode(const uint8_t *in, size_t in_size, char *out, size_t out_size)
{
const char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -38,11 +22,11 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_encode(const uint8_t *in, size_t in_
// these three 8-bit (ASCII) characters become one 24-bit number
n = ((uint32_t)in[x]) << 16;
- if((x+1) < in_size)
- n += ((uint32_t)in[x+1]) << 8;
+ if((x + 1) < in_size)
+ n += ((uint32_t)in[x + 1]) << 8;
- if((x+2) < in_size)
- n += in[x+2];
+ if((x + 2) < in_size)
+ n += in[x + 2];
// this 24-bit number gets separated into four 6-bit numbers
n0 = (uint8_t)(n >> 18) & 63;
@@ -61,7 +45,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_encode(const uint8_t *in, size_t in_
// if we have only two bytes available, then their encoding is
// spread out over three chars
- if((x+1) < in_size)
+ if((x + 1) < in_size)
{
if(result_index >= out_size)
return CHIAKI_ERR_BUF_TOO_SMALL;
@@ -70,7 +54,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_encode(const uint8_t *in, size_t in_
// if we have all three bytes available, then their encoding is spread
// out over four characters
- if((x+2) < in_size)
+ if((x + 2) < in_size)
{
if(result_index >= out_size)
return CHIAKI_ERR_BUF_TOO_SMALL;
@@ -80,9 +64,9 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_encode(const uint8_t *in, size_t in_
// create and add padding that is required if we did not have a multiple of 3
// number of characters available
- if (pad_count > 0)
+ if(pad_count > 0)
{
- for (; pad_count < 3; pad_count++)
+ for(; pad_count < 3; pad_count++)
{
if(result_index >= out_size)
return CHIAKI_ERR_BUF_TOO_SMALL;
@@ -95,25 +79,22 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_encode(const uint8_t *in, size_t in_
return CHIAKI_ERR_SUCCESS;
}
-
-
-
#define WHITESPACE 64
-#define EQUALS 65
-#define INVALID 66
+#define EQUALS 65
+#define INVALID 66
static const unsigned char d[] = {
- 66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
- 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,
- 54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 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,66,66,66,66,66,66,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,66,66,
- 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
- 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
- 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
- 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
- 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
- 66,66,66,66,66,66
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 62, 66, 66, 66, 63, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 66, 66, 66, 65, 66, 66, 66, 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, 66, 66, 66, 66, 66, 66, 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, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66
};
CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_decode(const char *in, size_t in_size, uint8_t *out, size_t *out_size)
@@ -123,17 +104,17 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_decode(const char *in, size_t in_siz
uint32_t buf = 0;
size_t len = 0;
- while (in < end)
+ while(in < end)
{
- unsigned char c = d[*in++];
+ unsigned char c = d[(size_t)(*in++)];
switch(c)
{
case WHITESPACE:
- continue; // skip whitespace
+ continue; // skip whitespace
case INVALID:
- return CHIAKI_ERR_INVALID_DATA; // invalid input
- case EQUALS: // pad character, end of data
+ return CHIAKI_ERR_INVALID_DATA; // invalid input
+ case EQUALS: // pad character, end of data
in = end;
continue;
default:
@@ -147,7 +128,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_decode(const char *in, size_t in_siz
*(out++) = (unsigned char)((buf >> 16) & 0xff);
*(out++) = (unsigned char)((buf >> 8) & 0xff);
*(out++) = (unsigned char)(buf & 0xff);
- buf = 0; iter = 0;
+ buf = 0;
+ iter = 0;
}
}
}
@@ -168,4 +150,4 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_base64_decode(const char *in, size_t in_siz
*out_size = len;
return CHIAKI_ERR_SUCCESS;
-}
\ No newline at end of file
+}
diff --git a/lib/src/common.c b/lib/src/common.c
index 824ed13..adb0fc6 100644
--- a/lib/src/common.c
+++ b/lib/src/common.c
@@ -1,29 +1,14 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
-#include
#include
+#include
#include
+#include
#include
#include
-#include
#ifdef _WIN32
#include
@@ -113,10 +98,25 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_lib_init()
WORD wsa_version = MAKEWORD(2, 2);
WSADATA wsa_data;
int err = WSAStartup(wsa_version, &wsa_data);
- if (err != 0)
+ if(err != 0)
return CHIAKI_ERR_NETWORK;
}
#endif
return CHIAKI_ERR_SUCCESS;
}
+
+CHIAKI_EXPORT const char *chiaki_codec_name(ChiakiCodec codec)
+{
+ switch(codec)
+ {
+ case CHIAKI_CODEC_H264:
+ return "H264";
+ case CHIAKI_CODEC_H265:
+ return "H265";
+ case CHIAKI_CODEC_H265_HDR:
+ return "H265/HDR";
+ default:
+ return "unknown";
+ }
+}
diff --git a/lib/src/congestioncontrol.c b/lib/src/congestioncontrol.c
index d27c15d..8fb912b 100644
--- a/lib/src/congestioncontrol.c
+++ b/lib/src/congestioncontrol.c
@@ -1,26 +1,9 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
-
#define CONGESTION_CONTROL_INTERVAL_MS 200
-
static void *congestion_control_thread_func(void *user)
{
ChiakiCongestionControl *control = user;
@@ -35,8 +18,14 @@ static void *congestion_control_thread_func(void *user)
if(err != CHIAKI_ERR_TIMEOUT)
break;
- //CHIAKI_LOGD(control->takion->log, "Sending Congestion Control Packet");
- ChiakiTakionCongestionPacket packet = { 0 }; // TODO: fill with real values
+ uint64_t received;
+ uint64_t lost;
+ chiaki_packet_stats_get(control->stats, true, &received, &lost);
+ ChiakiTakionCongestionPacket packet = { 0 };
+ packet.received = (uint16_t)received;
+ packet.lost = (uint16_t)lost;
+ CHIAKI_LOGV(control->takion->log, "Sending Congestion Control Packet, received: %u, lost: %u",
+ (unsigned int)packet.received, (unsigned int)packet.lost);
chiaki_takion_send_congestion(control->takion, &packet);
}
@@ -44,9 +33,10 @@ static void *congestion_control_thread_func(void *user)
return NULL;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_congestion_control_start(ChiakiCongestionControl *control, ChiakiTakion *takion)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_congestion_control_start(ChiakiCongestionControl *control, ChiakiTakion *takion, ChiakiPacketStats *stats)
{
control->takion = takion;
+ control->stats = stats;
ChiakiErrorCode err = chiaki_bool_pred_cond_init(&control->stop_cond);
if(err != CHIAKI_ERR_SUCCESS)
diff --git a/lib/src/controller.c b/lib/src/controller.c
index f71371c..744a83f 100644
--- a/lib/src/controller.c
+++ b/lib/src/controller.c
@@ -1,33 +1,114 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
+#define TOUCH_ID_MASK 0x7f
+
CHIAKI_EXPORT void chiaki_controller_state_set_idle(ChiakiControllerState *state)
{
state->buttons = 0;
+ state->l2_state = 0;
+ state->r2_state = 0;
state->left_x = 0;
state->left_y = 0;
state->right_x = 0;
state->right_y = 0;
+ state->touch_id_next = 0;
+ for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
+ {
+ state->touches[i].id = -1;
+ state->touches[i].x = 0;
+ state->touches[i].y = 0;
+ }
+ state->gyro_x = state->gyro_y = state->gyro_z = 0.0f;
+ state->accel_x = 0.0f;
+ state->accel_y = 1.0f;
+ state->accel_z = 0.0f;
+ state->orient_x = 0.0f;
+ state->orient_y = 0.0f;
+ state->orient_z = 0.0f;
+ state->orient_w = 1.0f;
}
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define ABS(a) ((a) > 0 ? (a) : -(a))
+CHIAKI_EXPORT int8_t chiaki_controller_state_start_touch(ChiakiControllerState *state, uint16_t x, uint16_t y)
+{
+ for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
+ {
+ if(state->touches[i].id < 0)
+ {
+ state->touches[i].id = state->touch_id_next;
+ state->touch_id_next = (state->touch_id_next + 1) & TOUCH_ID_MASK;
+ state->touches[i].x = x;
+ state->touches[i].y = y;
+ return state->touches[i].id;
+ }
+ }
+ return -1;
+}
+
+CHIAKI_EXPORT void chiaki_controller_state_stop_touch(ChiakiControllerState *state, uint8_t id)
+{
+ for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
+ {
+ if(state->touches[i].id == id)
+ {
+ state->touches[i].id = -1;
+ break;
+ }
+ }
+}
+
+CHIAKI_EXPORT void chiaki_controller_state_set_touch_pos(ChiakiControllerState *state, uint8_t id, uint16_t x, uint16_t y)
+{
+ id &= TOUCH_ID_MASK;
+ for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
+ {
+ if(state->touches[i].id == id)
+ {
+ state->touches[i].x = x;
+ state->touches[i].y = y;
+ break;
+ }
+ }
+}
+
+CHIAKI_EXPORT bool chiaki_controller_state_equals(ChiakiControllerState *a, ChiakiControllerState *b)
+{
+ if(!(a->buttons == b->buttons
+ && a->l2_state == b->l2_state
+ && a->r2_state == b->r2_state
+ && a->left_x == b->left_x
+ && a->left_y == b->left_y
+ && a->right_x == b->right_x
+ && a->right_y == b->right_y))
+ return false;
+
+ for(size_t i=0; itouches[i].id != b->touches[i].id)
+ return false;
+ if(a->touches[i].id >= 0 && (a->touches[i].x != b->touches[i].x || a->touches[i].y != b->touches[i].y))
+ return false;
+ }
+
+#define CHECKF(n) if(a->n < b->n - 0.0000001f || a->n > b->n + 0.0000001f) return false
+ CHECKF(gyro_x);
+ CHECKF(gyro_y);
+ CHECKF(gyro_z);
+ CHECKF(accel_x);
+ CHECKF(accel_y);
+ CHECKF(accel_z);
+ CHECKF(orient_x);
+ CHECKF(orient_y);
+ CHECKF(orient_z);
+ CHECKF(orient_w);
+#undef CHECKF
+
+ return true;
+}
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) > 0 ? (a) : -(a))
#define MAX_ABS(a, b) (ABS(a) > ABS(b) ? (a) : (b))
CHIAKI_EXPORT void chiaki_controller_state_or(ChiakiControllerState *out, ChiakiControllerState *a, ChiakiControllerState *b)
@@ -39,4 +120,31 @@ CHIAKI_EXPORT void chiaki_controller_state_or(ChiakiControllerState *out, Chiaki
out->left_y = MAX_ABS(a->left_y, b->left_y);
out->right_x = MAX_ABS(a->right_x, b->right_x);
out->right_y = MAX_ABS(a->right_y, b->right_y);
+
+ #define ORF(n, idle_val) if(a->n == idle_val) out->n = b->n; else out->n = a->n
+ ORF(accel_x, 0.0f);
+ ORF(accel_y, 1.0f);
+ ORF(accel_z, 0.0f);
+ ORF(gyro_x, 0.0f);
+ ORF(gyro_y, 0.0f);
+ ORF(gyro_z, 0.0f);
+ ORF(orient_x, 0.0f);
+ ORF(orient_y, 0.0f);
+ ORF(orient_z, 0.0f);
+ ORF(orient_w, 1.0f);
+ #undef ORF
+
+ out->touch_id_next = 0;
+ for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
+ {
+ ChiakiControllerTouch *touch = a->touches[i].id >= 0 ? &a->touches[i] : (b->touches[i].id >= 0 ? &b->touches[i] : NULL);
+ if(!touch)
+ {
+ out->touches[i].id = -1;
+ out->touches[i].x = out->touches[i].y = 0;
+ continue;
+ }
+ if(touch != &out->touches[i])
+ out->touches[i] = *touch;
+ }
}
diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c
index daeee37..29a39b2 100644
--- a/lib/src/ctrl.c
+++ b/lib/src/ctrl.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -50,7 +35,14 @@ typedef enum ctrl_message_type_t {
CTRL_MESSAGE_TYPE_HEARTBEAT_REP = 0x1fe,
CTRL_MESSAGE_TYPE_LOGIN_PIN_REQ = 0x4,
CTRL_MESSAGE_TYPE_LOGIN_PIN_REP = 0x8004,
- CTRL_MESSAGE_TYPE_LOGIN = 0x5
+ CTRL_MESSAGE_TYPE_LOGIN = 0x5,
+ CTRL_MESSAGE_TYPE_GOTO_BED = 0x50,
+ CTRL_MESSAGE_TYPE_KEYBOARD_ENABLE_TOGGLE = 0x20,
+ CTRL_MESSAGE_TYPE_KEYBOARD_OPEN = 0x21,
+ CTRL_MESSAGE_TYPE_KEYBOARD_CLOSE_REMOTE = 0x22,
+ CTRL_MESSAGE_TYPE_KEYBOARD_TEXT_CHANGE_REQ = 0x23,
+ CTRL_MESSAGE_TYPE_KEYBOARD_TEXT_CHANGE_RES = 0x24,
+ CTRL_MESSAGE_TYPE_KEYBOARD_CLOSE_REQ = 0x25,
} CtrlMessageType;
typedef enum ctrl_login_state_t {
@@ -58,15 +50,54 @@ typedef enum ctrl_login_state_t {
CTRL_LOGIN_STATE_PIN_INCORRECT = 0x1
} CtrlLoginState;
+struct chiaki_ctrl_message_queue_t
+{
+ ChiakiCtrlMessageQueue *next;
+ uint16_t type;
+ uint8_t *payload;
+ size_t payload_size;
+};
+
+typedef struct ctrl_keyboard_open_t
+{
+ uint8_t unk[0x1C];
+ uint32_t text_length;
+} CtrlKeyboardOpenMessage;
+
+typedef struct ctrl_keyboard_text_request_t
+{
+ uint32_t counter;
+ uint32_t text_length1;
+ uint8_t unk1[0x8];
+ uint8_t unk2[0x10];
+ uint32_t text_length2;
+} CtrlKeyboardTextRequestMessage;
+
+typedef struct ctrl_keyboard_text_response_t
+{
+ uint32_t counter;
+ uint32_t unk;
+ uint32_t text_length1;
+ uint32_t unk2;
+ uint8_t unk3[0x10];
+ uint32_t unk4;
+ uint32_t text_length2;
+} CtrlKeyboardTextResponseMessage;
+
+void chiaki_session_send_event(ChiakiSession *session, ChiakiEvent *event);
static void *ctrl_thread_func(void *user);
-static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, const uint8_t *payload, size_t payload_size);
+static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, uint16_t type, const uint8_t *payload, size_t payload_size);
+static void ctrl_enable_optional_features(ChiakiCtrl *ctrl);
static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
static void ctrl_message_received_heartbeat_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
static void ctrl_message_received_login_pin_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
static void ctrl_message_received_login(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
+static void ctrl_message_received_keyboard_open(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
+static void ctrl_message_received_keyboard_close(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
+static void ctrl_message_received_keyboard_text_change(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size);
-CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, ChiakiSession *session)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_init(ChiakiCtrl *ctrl, ChiakiSession *session)
{
ctrl->session = session;
@@ -75,6 +106,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, ChiakiSession
ctrl->login_pin_requested = false;
ctrl->login_pin = NULL;
ctrl->login_pin_size = 0;
+ ctrl->msg_queue = NULL;
+ ctrl->keyboard_text_counter = 0;
ChiakiErrorCode err = chiaki_stop_pipe_init(&ctrl->notif_pipe);
if(err != CHIAKI_ERR_SUCCESS)
@@ -84,20 +117,23 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, ChiakiSession
if(err != CHIAKI_ERR_SUCCESS)
goto error_notif_pipe;
- err = chiaki_thread_create(&ctrl->thread, ctrl_thread_func, ctrl);
- if(err != CHIAKI_ERR_SUCCESS)
- goto error_notif_mutex;
-
- chiaki_thread_set_name(&ctrl->thread, "Chiaki Ctrl");
return err;
-
-error_notif_mutex:
chiaki_mutex_fini(&ctrl->notif_mutex);
error_notif_pipe:
chiaki_stop_pipe_fini(&ctrl->notif_pipe);
return err;
}
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl)
+{
+ ChiakiErrorCode err = chiaki_thread_create(&ctrl->thread, ctrl_thread_func, ctrl);
+ if(err != CHIAKI_ERR_SUCCESS)
+ return err;
+
+ chiaki_thread_set_name(&ctrl->thread, "Chiaki Ctrl");
+ return err;
+}
+
CHIAKI_EXPORT void chiaki_ctrl_stop(ChiakiCtrl *ctrl)
{
ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->notif_mutex);
@@ -109,11 +145,59 @@ CHIAKI_EXPORT void chiaki_ctrl_stop(ChiakiCtrl *ctrl)
CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_join(ChiakiCtrl *ctrl)
{
- ChiakiErrorCode err = chiaki_thread_join(&ctrl->thread, NULL);
+ return chiaki_thread_join(&ctrl->thread, NULL);
+}
+
+CHIAKI_EXPORT void chiaki_ctrl_fini(ChiakiCtrl *ctrl)
+{
chiaki_stop_pipe_fini(&ctrl->notif_pipe);
chiaki_mutex_fini(&ctrl->notif_mutex);
free(ctrl->login_pin);
- return err;
+}
+
+static void ctrl_message_queue_free(ChiakiCtrlMessageQueue *queue)
+{
+ free(queue->payload);
+ free(queue);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_send_message(ChiakiCtrl *ctrl, uint16_t type, const uint8_t *payload, size_t payload_size)
+{
+ ChiakiCtrlMessageQueue *queue = CHIAKI_NEW(ChiakiCtrlMessageQueue);
+ if(!queue)
+ return CHIAKI_ERR_MEMORY;
+ queue->next = NULL;
+ queue->type = type;
+ if(payload)
+ {
+ queue->payload = malloc(payload_size);
+ if(!queue->payload)
+ {
+ free(queue);
+ return CHIAKI_ERR_MEMORY;
+ }
+ memcpy(queue->payload, payload, payload_size);
+ queue->payload_size = payload_size;
+ }
+ else
+ {
+ queue->payload = NULL;
+ queue->payload_size = 0;
+ }
+ ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->notif_mutex);
+ assert(err == CHIAKI_ERR_SUCCESS);
+ if(!ctrl->msg_queue)
+ ctrl->msg_queue = queue;
+ else
+ {
+ ChiakiCtrlMessageQueue *c = ctrl->msg_queue;
+ while(c->next)
+ c = c->next;
+ c->next = queue;
+ }
+ chiaki_mutex_unlock(&ctrl->notif_mutex);
+ chiaki_stop_pipe_stop(&ctrl->notif_pipe);
+ return CHIAKI_ERR_SUCCESS;
}
CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, const uint8_t *pin, size_t pin_size)
@@ -133,6 +217,45 @@ CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, const uint8_t *pi
chiaki_mutex_unlock(&ctrl->notif_mutex);
}
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_goto_bed(ChiakiCtrl *ctrl)
+{
+ return chiaki_ctrl_send_message(ctrl, CTRL_MESSAGE_TYPE_GOTO_BED, NULL, 0);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_keyboard_set_text(ChiakiCtrl *ctrl, const char *text)
+{
+ const uint32_t length = strlen(text);
+ const size_t payload_size = sizeof(CtrlKeyboardTextRequestMessage) + length;
+
+ uint8_t *payload = malloc(payload_size);
+ if(!payload)
+ return CHIAKI_ERR_MEMORY;
+ memset(payload, 0, payload_size);
+ memcpy(payload + sizeof(CtrlKeyboardTextRequestMessage), text, length);
+
+ CtrlKeyboardTextRequestMessage *msg = (CtrlKeyboardTextRequestMessage *)payload;
+ msg->counter = ntohl(++ctrl->keyboard_text_counter);
+ msg->text_length1 = ntohl(length);
+ msg->text_length2 = ntohl(length);
+
+ ChiakiErrorCode err;
+ err = chiaki_ctrl_send_message(ctrl, CTRL_MESSAGE_TYPE_KEYBOARD_TEXT_CHANGE_REQ, payload, payload_size);
+
+ free(payload);
+ return err;
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_keyboard_accept(ChiakiCtrl *ctrl)
+{
+ const uint8_t accept[4] = { 0x00, 0x00, 0x00, 0x00 };
+ return chiaki_ctrl_send_message(ctrl, CTRL_MESSAGE_TYPE_KEYBOARD_CLOSE_REQ, accept, 4);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_keyboard_reject(ChiakiCtrl *ctrl)
+{
+ const uint8_t reject[4] = { 0x00, 0x00, 0x00, 0x01 };
+ return chiaki_ctrl_send_message(ctrl, CTRL_MESSAGE_TYPE_KEYBOARD_CLOSE_REQ, reject, 4);
+}
static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl);
static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t *payload, size_t payload_size);
@@ -200,12 +323,17 @@ static void *ctrl_thread_func(void *user)
chiaki_mutex_unlock(&ctrl->notif_mutex);
err = chiaki_stop_pipe_select_single(&ctrl->notif_pipe, ctrl->sock, false, UINT64_MAX);
chiaki_mutex_lock(&ctrl->notif_mutex);
+
+ bool msg_queue_updated = false;
if(err == CHIAKI_ERR_CANCELED)
{
- if(ctrl->should_stop)
+ while(ctrl->msg_queue)
{
- CHIAKI_LOGI(ctrl->session->log, "Ctrl requested to stop");
- break;
+ ctrl_message_send(ctrl, ctrl->msg_queue->type, ctrl->msg_queue->payload, ctrl->msg_queue->payload_size);
+ ChiakiCtrlMessageQueue *next = ctrl->msg_queue->next;
+ ctrl_message_queue_free(ctrl->msg_queue);
+ ctrl->msg_queue = next;
+ msg_queue_updated = true;
}
if(ctrl->login_pin_entered)
@@ -217,12 +345,19 @@ static void *ctrl_thread_func(void *user)
ctrl->login_pin = NULL;
ctrl->login_pin_size = 0;
chiaki_stop_pipe_reset(&ctrl->notif_pipe);
+ continue;
}
- else
+
+ if(ctrl->should_stop)
{
- CHIAKI_LOGE(ctrl->session->log, "Ctrl notif pipe set without state");
+ CHIAKI_LOGI(ctrl->session->log, "Ctrl requested to stop");
break;
}
+
+ if(msg_queue_updated)
+ chiaki_stop_pipe_reset(&ctrl->notif_pipe);
+
+ continue;
}
else if(err != CHIAKI_ERR_SUCCESS)
{
@@ -251,10 +386,15 @@ static void *ctrl_thread_func(void *user)
return NULL;
}
-static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, const uint8_t *payload, size_t payload_size)
+static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, uint16_t type, const uint8_t *payload, size_t payload_size)
{
assert(payload_size == 0 || payload);
+ CHIAKI_LOGV(ctrl->session->log, "Ctrl sending message type %x, size %llx\n",
+ (unsigned int)type, (unsigned long long)payload_size);
+ if(payload)
+ chiaki_log_hexdump(ctrl->session->log, CHIAKI_LOG_VERBOSE, payload, payload_size);
+
uint8_t *enc = NULL;
if(payload && payload_size)
{
@@ -319,6 +459,7 @@ static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t *
{
case CTRL_MESSAGE_TYPE_SESSION_ID:
ctrl_message_received_session_id(ctrl, payload, payload_size);
+ ctrl_enable_optional_features(ctrl);
break;
case CTRL_MESSAGE_TYPE_HEARTBEAT_REQ:
ctrl_message_received_heartbeat_req(ctrl, payload, payload_size);
@@ -329,13 +470,44 @@ static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t *
case CTRL_MESSAGE_TYPE_LOGIN:
ctrl_message_received_login(ctrl, payload, payload_size);
break;
- default:
+ case CTRL_MESSAGE_TYPE_KEYBOARD_OPEN:
+ ctrl_message_received_keyboard_open(ctrl, payload, payload_size);
+ break;
+ case CTRL_MESSAGE_TYPE_KEYBOARD_TEXT_CHANGE_RES:
+ ctrl_message_received_keyboard_text_change(ctrl, payload, payload_size);
+ break;
+ case CTRL_MESSAGE_TYPE_KEYBOARD_CLOSE_REMOTE:
+ ctrl_message_received_keyboard_close(ctrl, payload, payload_size);
+ break;
+ default:
CHIAKI_LOGW(ctrl->session->log, "Received Ctrl Message with unknown type %#x", msg_type);
chiaki_log_hexdump(ctrl->session->log, CHIAKI_LOG_WARNING, payload, payload_size);
break;
}
}
+static void ctrl_enable_optional_features(ChiakiCtrl *ctrl)
+{
+ if(ctrl->session->connect_info.enable_dualsense)
+ {
+ CHIAKI_LOGI(ctrl->session->log, "Enabling DualSense features");
+ const uint8_t enable[3] = { 0x00, 0x40, 0x00 };
+ ctrl_message_send(ctrl, 0x13, enable, 3);
+ }
+ if(ctrl->session->connect_info.enable_keyboard)
+ {
+ CHIAKI_LOGI(ctrl->session->log, "Enabling Keyboard");
+ // TODO: Last byte of pre_enable request is random (?)
+ // TODO: Signature ?!
+ uint8_t enable = 1;
+ uint8_t pre_enable[4] = { 0x00, 0x01, 0x01, 0x80 };
+ uint8_t signature[0x10] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ ctrl_message_send(ctrl, 0xD, signature, 0x10);
+ ctrl_message_send(ctrl, 0x36, pre_enable, 4);
+ ctrl_message_send(ctrl, CTRL_MESSAGE_TYPE_KEYBOARD_ENABLE_TOGGLE, &enable, 1);
+ ctrl_message_send(ctrl, 0x36, pre_enable, 4);
+ }
+}
static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size)
{
@@ -452,8 +624,67 @@ static void ctrl_message_received_login(ChiakiCtrl *ctrl, uint8_t *payload, size
}
}
+static void ctrl_message_received_keyboard_open(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size)
+{
+ assert(payload_size >= sizeof(CtrlKeyboardOpenMessage));
-typedef struct ctrl_response_t {
+ CtrlKeyboardOpenMessage *msg = (CtrlKeyboardOpenMessage *)payload;
+ msg->text_length = ntohl(msg->text_length);
+ assert(payload_size == sizeof(CtrlKeyboardOpenMessage) + msg->text_length);
+
+ uint8_t *buffer = msg->text_length > 0 ? malloc((size_t)msg->text_length + 1) : NULL;
+ if(buffer)
+ {
+ buffer[msg->text_length] = '\0';
+ memcpy(buffer, payload + sizeof(CtrlKeyboardOpenMessage), msg->text_length);
+ }
+
+ ChiakiEvent keyboard_event;
+ keyboard_event.type = CHIAKI_EVENT_KEYBOARD_OPEN;
+ keyboard_event.keyboard.text_str = (const char *)buffer;
+ chiaki_session_send_event(ctrl->session, &keyboard_event);
+
+ if(buffer)
+ free(buffer);
+}
+
+static void ctrl_message_received_keyboard_close(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size)
+{
+ (void)payload;
+ (void)payload_size;
+
+ ChiakiEvent keyboard_event;
+ keyboard_event.type = CHIAKI_EVENT_KEYBOARD_REMOTE_CLOSE;
+ keyboard_event.keyboard.text_str = NULL;
+ chiaki_session_send_event(ctrl->session, &keyboard_event);
+}
+
+static void ctrl_message_received_keyboard_text_change(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size)
+{
+ assert(payload_size >= sizeof(CtrlKeyboardTextResponseMessage));
+
+ CtrlKeyboardTextResponseMessage *msg = (CtrlKeyboardTextResponseMessage *)payload;
+ msg->text_length1 = ntohl(msg->text_length1);
+ assert(payload_size == sizeof(CtrlKeyboardTextResponseMessage) + msg->text_length1);
+
+ uint8_t *buffer = msg->text_length1 > 0 ? malloc((size_t)msg->text_length1 + 1) : NULL;
+ if(buffer)
+ {
+ buffer[msg->text_length1] = '\0';
+ memcpy(buffer, payload + sizeof(CtrlKeyboardTextResponseMessage), msg->text_length1);
+ }
+
+ ChiakiEvent keyboard_event;
+ keyboard_event.type = CHIAKI_EVENT_KEYBOARD_TEXT_CHANGE;
+ keyboard_event.keyboard.text_str = (const char *)buffer;
+ chiaki_session_send_event(ctrl->session, &keyboard_event);
+
+ if(buffer)
+ free(buffer);
+}
+
+typedef struct ctrl_response_t
+{
bool server_type_valid;
uint8_t rp_server_type[0x10];
bool success;
@@ -519,7 +750,6 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl)
if(err != CHIAKI_ERR_SUCCESS)
CHIAKI_LOGE(session->log, "Failed to set ctrl socket to non-blocking: %s", chiaki_error_string(err));
-
chiaki_mutex_unlock(&ctrl->notif_mutex);
err = chiaki_stop_pipe_connect(&ctrl->notif_pipe, sock, sa, addr->ai_addrlen);
chiaki_mutex_lock(&ctrl->notif_mutex);
@@ -575,9 +805,57 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl)
if(err != CHIAKI_ERR_SUCCESS)
goto error;
+ char bitrate_b64[256];
+ bool have_bitrate = session->target >= CHIAKI_TARGET_PS4_10;
+ if(have_bitrate)
+ {
+ uint8_t bitrate[4] = { 0 };
+ uint8_t bitrate_enc[4] = { 0 };
+ err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (const uint8_t *)bitrate, bitrate_enc, 4);
+ if(err != CHIAKI_ERR_SUCCESS)
+ goto error;
+
+ err = chiaki_base64_encode(bitrate_enc, 4, bitrate_b64, sizeof(bitrate_b64));
+ if(err != CHIAKI_ERR_SUCCESS)
+ goto error;
+ }
+
+ char streaming_type_b64[256];
+ bool have_streaming_type = chiaki_target_is_ps5(session->target);
+ if(have_streaming_type)
+ {
+ uint32_t streaming_type;
+ switch(session->connect_info.video_profile.codec)
+ {
+ case CHIAKI_CODEC_H265:
+ streaming_type = 2;
+ break;
+ case CHIAKI_CODEC_H265_HDR:
+ streaming_type = 3;
+ break;
+ default:
+ streaming_type = 1;
+ break;
+ }
+ uint8_t streaming_type_buf[4] = {
+ streaming_type & 0xff,
+ (streaming_type >> 8) & 0xff,
+ (streaming_type >> 0x10) & 0xff,
+ (streaming_type >> 0x18) & 0xff
+ };
+ uint8_t streaming_type_enc[4] = { 0 };
+ err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++,
+ streaming_type_buf, streaming_type_enc, 4);
+ if(err != CHIAKI_ERR_SUCCESS)
+ goto error;
+
+ err = chiaki_base64_encode(streaming_type_enc, 4, streaming_type_b64, sizeof(streaming_type_b64));
+ if(err != CHIAKI_ERR_SUCCESS)
+ goto error;
+ }
static const char request_fmt[] =
- "GET /sce/rp/session/ctrl HTTP/1.1\r\n"
+ "GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"User-Agent: remoteplay Windows\r\n"
"Connection: keep-alive\r\n"
@@ -588,17 +866,35 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl)
"RP-ControllerType: 3\r\n"
"RP-ClientType: 11\r\n"
"RP-OSType: %s\r\n"
- "RP-ConPath: 1\r\n\r\n";
+ "RP-ConPath: 1\r\n"
+ "%s%s%s"
+ "%s%s%s"
+ "\r\n";
- const char *rp_version = chiaki_rp_version_string(session->rp_version);
+ const char *path;
+ if(session->target == CHIAKI_TARGET_PS4_8 || session->target == CHIAKI_TARGET_PS4_9)
+ path = "/sce/rp/session/ctrl";
+ else if(chiaki_target_is_ps5(session->target))
+ path = "/sie/ps5/rp/sess/ctrl";
+ else
+ path = "/sie/ps4/rp/sess/ctrl";
+ const char *rp_version = chiaki_rp_version_string(session->target);
char buf[512];
int request_len = snprintf(buf, sizeof(buf), request_fmt,
- session->connect_info.hostname, SESSION_CTRL_PORT, auth_b64, rp_version ? rp_version : "", did_b64, ostype_b64);
+ path, session->connect_info.hostname, SESSION_CTRL_PORT, auth_b64,
+ rp_version ? rp_version : "", did_b64, ostype_b64,
+ have_bitrate ? "RP-StartBitrate: " : "",
+ have_bitrate ? bitrate_b64 : "",
+ have_bitrate ? "\r\n" : "",
+ have_streaming_type ? "RP-StreamingType: " : "",
+ have_streaming_type ? streaming_type_b64 : "",
+ have_streaming_type ? "\r\n" : "");
if(request_len < 0 || request_len >= sizeof(buf))
goto error;
CHIAKI_LOGI(session->log, "Sending ctrl request");
+ chiaki_log_hexdump(session->log, CHIAKI_LOG_VERBOSE, (const uint8_t *)buf, (size_t)request_len);
int sent = send(sock, buf, (size_t)request_len, 0);
if(sent < 0)
@@ -674,7 +970,32 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl)
}
}
- if(!response.server_type_valid)
+ if(response.server_type_valid)
+ {
+ uint8_t server_type = response.rp_server_type[0]; // 0 = PS4, 1 = PS4 Pro, 2 = PS5
+ CHIAKI_LOGI(session->log, "Ctrl got Server Type: %u", (unsigned int)server_type);
+ if(server_type == 0
+ && session->connect_info.video_profile_auto_downgrade
+ && session->connect_info.video_profile.height == 1080)
+ {
+ // regular PS4 doesn't support >= 1080p
+ CHIAKI_LOGI(session->log, "1080p was selected but server would not support it. Downgrading.");
+ chiaki_connect_video_profile_preset(
+ &session->connect_info.video_profile,
+ CHIAKI_VIDEO_RESOLUTION_PRESET_720p,
+ session->connect_info.video_profile.max_fps == 60
+ ? CHIAKI_VIDEO_FPS_PRESET_60
+ : CHIAKI_VIDEO_FPS_PRESET_30);
+ }
+ if((server_type == 0 || server_type == 1)
+ && session->connect_info.video_profile.codec != CHIAKI_CODEC_H264)
+ {
+ // PS4 doesn't support anything except h264
+ CHIAKI_LOGI(session->log, "A codec other than H264 was selected but server would not support it. Downgrading.");
+ session->connect_info.video_profile.codec = CHIAKI_CODEC_H264;
+ }
+ }
+ else
CHIAKI_LOGE(session->log, "No valid Server Type in ctrl response");
ctrl->sock = sock;
diff --git a/lib/src/discovery.c b/lib/src/discovery.c
index e2d5205..f207a54 100644
--- a/lib/src/discovery.c
+++ b/lib/src/discovery.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include "utils.h"
@@ -47,14 +32,47 @@ const char *chiaki_discovery_host_state_string(ChiakiDiscoveryHostState state)
}
}
+CHIAKI_EXPORT bool chiaki_discovery_host_is_ps5(ChiakiDiscoveryHost *host)
+{
+ return host->device_discovery_protocol_version
+ && !strcmp(host->device_discovery_protocol_version, CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5);
+}
+
+CHIAKI_EXPORT ChiakiTarget chiaki_discovery_host_system_version_target(ChiakiDiscoveryHost *host)
+{
+ // traslate discovered system_version into ChiakiTarget
+
+ int version = atoi(host->system_version);
+ bool is_ps5 = chiaki_discovery_host_is_ps5(host);
+
+ if(version >= 8050001 && is_ps5)
+ // PS5 >= 1.0
+ return CHIAKI_TARGET_PS5_1;
+ if(version >= 8050000 && is_ps5)
+ // PS5 >= 0
+ return CHIAKI_TARGET_PS5_UNKNOWN;
+
+ if(version >= 8000000)
+ // PS4 >= 8.0
+ return CHIAKI_TARGET_PS4_10;
+ if(version >= 7000000)
+ // PS4 >= 7.0
+ return CHIAKI_TARGET_PS4_9;
+ if(version > 0)
+ return CHIAKI_TARGET_PS4_8;
+
+ return CHIAKI_TARGET_PS4_UNKNOWN;
+}
+
CHIAKI_EXPORT int chiaki_discovery_packet_fmt(char *buf, size_t buf_size, ChiakiDiscoveryPacket *packet)
{
- const char *version_str = packet->protocol_version ? packet->protocol_version : CHIAKI_DISCOVERY_PROTOCOL_VERSION;
+ if(!packet->protocol_version)
+ return -1;
switch(packet->cmd)
{
case CHIAKI_DISCOVERY_CMD_SRCH:
return snprintf(buf, buf_size, "SRCH * HTTP/1.1\ndevice-discovery-protocol-version:%s\n",
- version_str);
+ packet->protocol_version);
case CHIAKI_DISCOVERY_CMD_WAKEUP:
return snprintf(buf, buf_size,
"WAKEUP * HTTP/1.1\n"
@@ -64,7 +82,7 @@ CHIAKI_EXPORT int chiaki_discovery_packet_fmt(char *buf, size_t buf_size, Chiaki
"app-type:r\n"
"user-credential:%llu\n"
"device-discovery-protocol-version:%s\n",
- (unsigned long long)packet->user_credential, version_str);
+ (unsigned long long)packet->user_credential, packet->protocol_version);
default:
return -1;
}
@@ -127,30 +145,55 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_init(ChiakiDiscovery *discovery,
discovery->log = log;
- discovery->socket = socket(AF_INET, SOCK_DGRAM, 0);
+ discovery->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(CHIAKI_SOCKET_IS_INVALID(discovery->socket))
{
CHIAKI_LOGE(discovery->log, "Discovery failed to create socket");
return CHIAKI_ERR_NETWORK;
}
- memset(&discovery->local_addr, 0, sizeof(discovery->local_addr));
- discovery->local_addr.sa_family = family;
- if(family == AF_INET6)
+ // First try CHIAKI_DISCOVERY_PORT_LOCAL_MIN..local_addr;
- addr->sin6_addr = anyaddr;
- addr->sin6_port = htons(0);
- }
- else // AF_INET
- {
- struct sockaddr_in *addr = (struct sockaddr_in *)&discovery->local_addr;
- addr->sin_addr.s_addr = htonl(INADDR_ANY);
- addr->sin_port = htons(0);
+ memset(&discovery->local_addr, 0, sizeof(discovery->local_addr));
+ discovery->local_addr.sa_family = family;
+ if(family == AF_INET6)
+ {
+#ifndef __SWITCH__
+ struct in6_addr anyaddr = IN6ADDR_ANY_INIT;
+#endif
+ struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&discovery->local_addr;
+#ifndef __SWITCH__
+ addr->sin6_addr = anyaddr;
+#endif
+ addr->sin6_port = htons(port);
+ }
+ else // AF_INET
+ {
+ struct sockaddr_in *addr = (struct sockaddr_in *)&discovery->local_addr;
+ addr->sin_addr.s_addr = htonl(INADDR_ANY);
+ addr->sin_port = htons(port);
+ }
+
+ r = bind(discovery->socket, &discovery->local_addr, sizeof(discovery->local_addr));
+ if(r >= 0 || !port)
+ break;
+ if(port == CHIAKI_DISCOVERY_PORT_LOCAL_MAX)
+ {
+ port = 0;
+ CHIAKI_LOGI(discovery->log, "Discovery failed to bind port %u, trying random",
+ (unsigned int)port);
+ }
+ else
+ {
+ port++;
+ CHIAKI_LOGI(discovery->log, "Discovery failed to bind port %u, trying one higher",
+ (unsigned int)port);
+ }
}
- int r = bind(discovery->socket, &discovery->local_addr, sizeof(discovery->local_addr));
if(r < 0)
{
CHIAKI_LOGE(discovery->log, "Discovery failed to bind");
@@ -190,6 +233,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_send(ChiakiDiscovery *discovery,
if((size_t)len >= sizeof(buf))
return CHIAKI_ERR_BUF_TOO_SMALL;
+ CHIAKI_LOGV(discovery->log, "Discovery sending:");
+ chiaki_log_hexdump(discovery->log, CHIAKI_LOG_VERBOSE, (const uint8_t *)buf, (size_t)len + 1);
int rc = sendto_broadcast(discovery->log, discovery->socket, buf, (size_t)len + 1, 0, addr, addr_size);
if(rc < 0)
{
@@ -291,7 +336,7 @@ static void *discovery_thread_func(void *user)
return NULL;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_wakeup(ChiakiLog *log, ChiakiDiscovery *discovery, const char *host, uint64_t user_credential)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_wakeup(ChiakiLog *log, ChiakiDiscovery *discovery, const char *host, uint64_t user_credential, bool ps5)
{
struct addrinfo *addrinfos;
int r = getaddrinfo(host, NULL, NULL, &addrinfos); // TODO: this blocks, use something else
@@ -322,10 +367,11 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_wakeup(ChiakiLog *log, ChiakiDisc
return CHIAKI_ERR_UNKNOWN;
}
- ((struct sockaddr_in *)&addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT);
+ ((struct sockaddr_in *)&addr)->sin_port = htons(ps5 ? CHIAKI_DISCOVERY_PORT_PS5 : CHIAKI_DISCOVERY_PORT_PS4);
ChiakiDiscoveryPacket packet = { 0 };
packet.cmd = CHIAKI_DISCOVERY_CMD_WAKEUP;
+ packet.protocol_version = ps5 ? CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5 : CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS4;
packet.user_credential = user_credential;
ChiakiErrorCode err;
diff --git a/lib/src/discoveryservice.c b/lib/src/discoveryservice.c
index 2373907..f163092 100644
--- a/lib/src/discoveryservice.c
+++ b/lib/src/discoveryservice.c
@@ -1,25 +1,16 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
#include
+#ifdef _WIN32
+#include
+#else
+#include
+#endif
+
static void *discovery_service_thread_func(void *user);
static void discovery_service_ping(ChiakiDiscoveryService *service);
static void discovery_service_drop_old_hosts(ChiakiDiscoveryService *service);
@@ -150,9 +141,27 @@ static void discovery_service_ping(ChiakiDiscoveryService *service)
CHIAKI_LOGV(service->log, "Discovery Service sending ping");
ChiakiDiscoveryPacket packet = { 0 };
packet.cmd = CHIAKI_DISCOVERY_CMD_SRCH;
+ packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS4;
+ if(service->options.send_addr->sa_family == AF_INET)
+ ((struct sockaddr_in *)service->options.send_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS4);
+ else if(service->options.send_addr->sa_family == AF_INET6)
+ ((struct sockaddr_in6 *)service->options.send_addr)->sin6_port = htons(CHIAKI_DISCOVERY_PORT_PS4);
+ else
+ {
+ CHIAKI_LOGE(service->log, "Discovery Service send_addr has unknown sa_family");
+ return;
+ }
err = chiaki_discovery_send(&service->discovery, &packet, service->options.send_addr, service->options.send_addr_size);
if(err != CHIAKI_ERR_SUCCESS)
- CHIAKI_LOGE(service->log, "Discovery Service failed to send ping");
+ CHIAKI_LOGE(service->log, "Discovery Service failed to send ping for PS4");
+ packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5;
+ if(service->options.send_addr->sa_family == AF_INET)
+ ((struct sockaddr_in *)service->options.send_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS5);
+ else // if(service->options.send_addr->sa_family == AF_INET6)
+ ((struct sockaddr_in6 *)service->options.send_addr)->sin6_port = htons(CHIAKI_DISCOVERY_PORT_PS5);
+ err = chiaki_discovery_send(&service->discovery, &packet, service->options.send_addr, service->options.send_addr_size);
+ if(err != CHIAKI_ERR_SUCCESS)
+ CHIAKI_LOGE(service->log, "Discovery Service failed to send ping for PS5");
}
static void discovery_service_drop_old_hosts(ChiakiDiscoveryService *service)
@@ -229,7 +238,7 @@ static void discovery_service_host_received(ChiakiDiscoveryHost *host, void *use
if(service->hosts_count == service->options.hosts_max)
{
CHIAKI_LOGE(service->log, "Discovery Service received new host, but no space available");
- goto r2con;
+ goto rzcon;
}
CHIAKI_LOGI(service->log, "Discovery Service detected new host with id %s", host->host_id);
@@ -270,7 +279,7 @@ static void discovery_service_host_received(ChiakiDiscoveryHost *host, void *use
if(change)
discovery_service_report_state(service);
-r2con:
+rzcon:
chiaki_mutex_unlock(&service->state_mutex);
}
@@ -279,4 +288,4 @@ static void discovery_service_report_state(ChiakiDiscoveryService *service)
// service->state_mutex must be locked
if(service->options.cb)
service->options.cb(service->hosts, service->hosts_count, service->options.cb_user);
-}
\ No newline at end of file
+}
diff --git a/lib/src/ecdh.c b/lib/src/ecdh.c
index 3ae49e1..3277b60 100644
--- a/lib/src/ecdh.c
+++ b/lib/src/ecdh.c
@@ -1,40 +1,61 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
#include
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+#include "mbedtls/entropy.h"
+#include "mbedtls/md.h"
+#else
#include
#include
#include
#include
+#include
+#endif
+// memset
#include
#include
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_init(ChiakiECDH *ecdh)
{
memset(ecdh, 0, sizeof(ChiakiECDH));
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+#define CHECK(err) if((err) != 0) { \
+ chiaki_ecdh_fini(ecdh); \
+ return CHIAKI_ERR_UNKNOWN; }
+ // mbedtls ecdh example:
+ // https://github.com/ARMmbed/mbedtls/blob/development/programs/pkey/ecdh_curve25519.c
+ const char pers[] = "ecdh";
+ mbedtls_entropy_context entropy;
+ //init RNG Seed context
+ mbedtls_entropy_init(&entropy);
+ // init local key
+ //mbedtls_ecp_keypair_init(&ecdh->key_local);
+ mbedtls_ecdh_init(&ecdh->ctx);
+ // init ecdh group
+ // keep rng context in ecdh for later reuse
+ mbedtls_ctr_drbg_init(&ecdh->drbg);
+ // build RNG seed
+ CHECK(mbedtls_ctr_drbg_seed(&ecdh->drbg, mbedtls_entropy_func, &entropy,
+ (const unsigned char *) pers, sizeof pers));
+
+ // build MBEDTLS_ECP_DP_SECP256K1 group
+ CHECK(mbedtls_ecp_group_load(&ecdh->ctx.grp, MBEDTLS_ECP_DP_SECP256K1));
+ // build key
+ CHECK(mbedtls_ecdh_gen_public(&ecdh->ctx.grp, &ecdh->ctx.d,
+ &ecdh->ctx.Q, mbedtls_ctr_drbg_random, &ecdh->drbg));
+
+ // relese entropy ptr
+ mbedtls_entropy_free(&entropy);
+#undef CHECK
+
+#else
#define CHECK(a) if(!(a)) { chiaki_ecdh_fini(ecdh); return CHIAKI_ERR_UNKNOWN; }
-
CHECK(ecdh->group = EC_GROUP_new_by_curve_name(NID_secp256k1));
CHECK(ecdh->key_local = EC_KEY_new());
@@ -42,19 +63,47 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_init(ChiakiECDH *ecdh)
CHECK(EC_KEY_generate_key(ecdh->key_local));
#undef CHECK
+#endif
return CHIAKI_ERR_SUCCESS;
}
CHIAKI_EXPORT void chiaki_ecdh_fini(ChiakiECDH *ecdh)
{
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ mbedtls_ecdh_free(&ecdh->ctx);
+ mbedtls_ctr_drbg_free(&ecdh->drbg);
+#else
EC_KEY_free(ecdh->key_local);
EC_GROUP_free(ecdh->group);
+#endif
}
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_set_local_key(ChiakiECDH *ecdh, const uint8_t *private_key, size_t private_key_size, const uint8_t *public_key, size_t public_key_size)
{
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ //https://tls.mbed.org/discussions/generic/publickey-binary-data-in-der
+ // Load keys from buffers (i.e: config file)
+ // TODO test
+
+ // public
+ int r = 0;
+ r = mbedtls_ecp_point_read_binary(&ecdh->ctx.grp, &ecdh->ctx.Q, public_key, public_key_size);
+ if(r != 0)
+ return CHIAKI_ERR_UNKNOWN;
+
+ // secret
+ r = mbedtls_mpi_read_binary(&ecdh->ctx.d, private_key, private_key_size);
+ if(r != 0)
+ return CHIAKI_ERR_UNKNOWN;
+
+ // regen key
+ r = mbedtls_ecdh_gen_public(&ecdh->ctx.grp, &ecdh->ctx.d, &ecdh->ctx.Q, mbedtls_ctr_drbg_random, &ecdh->drbg);
+ if(r != 0)
+ return CHIAKI_ERR_UNKNOWN;
+
+ return CHIAKI_ERR_SUCCESS;
+#else
ChiakiErrorCode err = CHIAKI_ERR_SUCCESS;
BIGNUM *private_key_bn = BN_bin2bn(private_key, (int)private_key_size, NULL);
@@ -91,10 +140,40 @@ error_pub:
error_priv:
BN_free(private_key_bn);
return err;
+#endif
}
CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_get_local_pub_key(ChiakiECDH *ecdh, uint8_t *key_out, size_t *key_out_size, const uint8_t *handshake_key, uint8_t *sig_out, size_t *sig_out_size)
{
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ mbedtls_md_context_t ctx;
+ mbedtls_md_init(&ctx);
+
+#define GOTO_ERROR(err) do { \
+ if((err) !=0){ \
+ goto error; \
+ }} while(0)
+ // extract pub key to build dh shared secret
+ // this key is sent to the remote server
+ GOTO_ERROR(mbedtls_ecp_point_write_binary( &ecdh->ctx.grp, &ecdh->ctx.Q,
+ MBEDTLS_ECP_PF_UNCOMPRESSED, key_out_size, key_out, *key_out_size ));
+
+ // https://tls.mbed.org/module-level-design-hashing
+ // HMAC
+ GOTO_ERROR(mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256) , 1));
+ GOTO_ERROR(mbedtls_md_hmac_starts(&ctx, handshake_key, CHIAKI_HANDSHAKE_KEY_SIZE));
+ GOTO_ERROR(mbedtls_md_hmac_update(&ctx, key_out, *key_out_size));
+ GOTO_ERROR(mbedtls_md_hmac_finish(&ctx, sig_out));
+ // SHA256 = 8*32
+ *sig_out_size = 32;
+#undef GOTO_ERROR
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_SUCCESS;
+
+error:
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+#else
const EC_POINT *point = EC_KEY_get0_public_key(ecdh->key_local);
if(!point)
return CHIAKI_ERR_UNKNOWN;
@@ -105,12 +184,40 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_get_local_pub_key(ChiakiECDH *ecdh, ui
if(!HMAC(EVP_sha256(), handshake_key, CHIAKI_HANDSHAKE_KEY_SIZE, key_out, *key_out_size, sig_out, (unsigned int *)sig_out_size))
return CHIAKI_ERR_UNKNOWN;
-
return CHIAKI_ERR_SUCCESS;
+
+#endif
}
CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_derive_secret(ChiakiECDH *ecdh, uint8_t *secret_out, const uint8_t *remote_key, size_t remote_key_size, const uint8_t *handshake_key, const uint8_t *remote_sig, size_t remote_sig_size)
{
+ //compute DH shared key
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ // https://github.com/ARMmbed/mbedtls/blob/development/programs/pkey/ecdh_curve25519.c#L151
+#define GOTO_ERROR(err) do { \
+ if((err) !=0){ \
+ goto error;} \
+ } while(0)
+
+ GOTO_ERROR(mbedtls_mpi_lset(&ecdh->ctx.Qp.Z, 1));
+ // load Qp point form remote PK
+ GOTO_ERROR(mbedtls_ecp_point_read_binary(&ecdh->ctx.grp,
+ &ecdh->ctx.Qp, remote_key, remote_key_size));
+
+ // build shared secret (diffie-hellman)
+ GOTO_ERROR(mbedtls_ecdh_compute_shared(&ecdh->ctx.grp,
+ &ecdh->ctx.z, &ecdh->ctx.Qp, &ecdh->ctx.d,
+ mbedtls_ctr_drbg_random, &ecdh->drbg));
+
+ // export shared secret to data buffer
+ GOTO_ERROR(mbedtls_mpi_write_binary(&ecdh->ctx.z,
+ secret_out, CHIAKI_ECDH_SECRET_SIZE));
+
+ return CHIAKI_ERR_SUCCESS;
+error:
+ return CHIAKI_ERR_UNKNOWN;
+
+#else
EC_POINT *remote_public_key = EC_POINT_new(ecdh->group);
if(!remote_public_key)
return CHIAKI_ERR_UNKNOWN;
@@ -129,4 +236,5 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ecdh_derive_secret(ChiakiECDH *ecdh, uint8_
return CHIAKI_ERR_UNKNOWN;
return CHIAKI_ERR_SUCCESS;
+#endif
}
diff --git a/lib/src/fec.c b/lib/src/fec.c
index ea72c76..2441d4a 100644
--- a/lib/src/fec.c
+++ b/lib/src/fec.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -28,8 +13,10 @@ int *create_matrix(unsigned int k, unsigned int m)
return cauchy_original_coding_matrix(k, m, CHIAKI_FEC_WORDSIZE);
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_fec_decode(uint8_t *frame_buf, size_t unit_size, unsigned int k, unsigned int m, const unsigned int *erasures, size_t erasures_count)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_fec_decode(uint8_t *frame_buf, size_t unit_size, size_t stride, unsigned int k, unsigned int m, const unsigned int *erasures, size_t erasures_count)
{
+ if(stride < unit_size)
+ return CHIAKI_ERR_INVALID_DATA;
int *matrix = create_matrix(k, m);
if(!matrix)
return CHIAKI_ERR_MEMORY;
@@ -61,7 +48,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_fec_decode(uint8_t *frame_buf, size_t unit_
for(size_t i=0; i.
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#define _USE_MATH_DEFINES
#include
#include
@@ -24,32 +11,83 @@
#include
#endif
#include
+#include
-CHIAKI_EXPORT void chiaki_feedback_state_format(uint8_t *buf, ChiakiFeedbackState *state)
+#define GYRO_MIN -30.0f
+#define GYRO_MAX 30.0f
+#define ACCEL_MIN -5.0f
+#define ACCEL_MAX 5.0f
+
+static uint32_t compress_quat(float *q)
{
- buf[0x0] = 0xa0; // TODO
- buf[0x1] = 0xff; // TODO
- buf[0x2] = 0x7f; // TODO
- buf[0x3] = 0xff; // TODO
- buf[0x4] = 0x7f; // TODO
- buf[0x5] = 0xff; // TODO
- buf[0x6] = 0x7f; // TODO
- buf[0x7] = 0xff; // TODO
- buf[0x8] = 0x7f; // TODO
- buf[0x9] = 0x99; // TODO
- buf[0xa] = 0x99; // TODO
- buf[0xb] = 0xff; // TODO
- buf[0xc] = 0x7f; // TODO
- buf[0xd] = 0xfe; // TODO
- buf[0xe] = 0xf7; // TODO
- buf[0xf] = 0xef; // TODO
- buf[0x10] = 0x1f; // TODO
+ // very similar idea as https://github.com/jpreiss/quatcompress
+ size_t largest_i = 0;
+ for(size_t i = 1; i < 4; i++)
+ {
+ if(fabs(q[i]) > fabs(q[largest_i]))
+ largest_i = i;
+ }
+ uint32_t r = (q[largest_i] < 0.0 ? 1 : 0) | (largest_i << 1);
+ for(size_t i = 0; i < 3; i++)
+ {
+ size_t qi = i < largest_i ? i : i + 1;
+ float v = q[qi];
+ if(v < -M_SQRT1_2)
+ v = -M_SQRT1_2;
+ if(v > M_SQRT1_2)
+ v = M_SQRT1_2;
+ v += M_SQRT1_2;
+ v *= (float)0x1ff / (2.0f * M_SQRT1_2);
+ r |= (uint32_t)v << (3 + i * 9);
+ }
+ return r;
+}
+
+CHIAKI_EXPORT void chiaki_feedback_state_format_v9(uint8_t *buf, ChiakiFeedbackState *state)
+{
+ buf[0x0] = 0xa0;
+ uint16_t v = (uint16_t)(0xffff * ((float)state->gyro_x - GYRO_MIN) / (GYRO_MAX - GYRO_MIN));
+ buf[0x1] = v;
+ buf[0x2] = v >> 8;
+ v = (uint16_t)(0xffff * ((float)state->gyro_y - GYRO_MIN) / (GYRO_MAX - GYRO_MIN));
+ buf[0x3] = v;
+ buf[0x4] = v >> 8;
+ v = (uint16_t)(0xffff * ((float)state->gyro_z - GYRO_MIN) / (GYRO_MAX - GYRO_MIN));
+ buf[0x5] = v;
+ buf[0x6] = v >> 8;
+ v = (uint16_t)(0xffff * ((float)state->accel_x - ACCEL_MIN) / (ACCEL_MAX - ACCEL_MIN));
+ buf[0x7] = v;
+ buf[0x8] = v >> 8;
+ v = (uint16_t)(0xffff * ((float)state->accel_y - ACCEL_MIN) / (ACCEL_MAX - ACCEL_MIN));
+ buf[0x9] = v;
+ buf[0xa] = v >> 8;
+ v = (uint16_t)(0xffff * ((float)state->accel_z - ACCEL_MIN) / (ACCEL_MAX - ACCEL_MIN));
+ buf[0xb] = v;
+ buf[0xc] = v >> 8;
+ float q[4] = { state->orient_x, state->orient_y, state->orient_z, state->orient_w };
+ uint32_t qc = compress_quat(q);
+ buf[0xd] = qc;
+ buf[0xe] = qc >> 0x8;
+ buf[0xf] = qc >> 0x10;
+ buf[0x10] = qc >> 0x18;
*((chiaki_unaligned_uint16_t *)(buf + 0x11)) = htons((uint16_t)state->left_x);
*((chiaki_unaligned_uint16_t *)(buf + 0x13)) = htons((uint16_t)state->left_y);
*((chiaki_unaligned_uint16_t *)(buf + 0x15)) = htons((uint16_t)state->right_x);
*((chiaki_unaligned_uint16_t *)(buf + 0x17)) = htons((uint16_t)state->right_y);
}
+CHIAKI_EXPORT void chiaki_feedback_state_format_v12(uint8_t *buf, ChiakiFeedbackState *state)
+{
+ chiaki_feedback_state_format_v9(buf, state);
+ buf[0x19] = 0x0;
+ buf[0x1a] = 0x0;
+
+ // 1 is classic DualShock, 0 is DualSense, but using 0 requires setting [0x19] and [0x1a] to
+ // values taken from raw HID, which is generally not available. But setting 1 for both seems
+ // to always work fine.
+ buf[0x1b] = 0x1;
+}
+
CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_history_event_set_button(ChiakiFeedbackHistoryEvent *event, uint64_t button, uint8_t state)
{
// some buttons use a third byte for the state, some don't
@@ -119,6 +157,17 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_history_event_set_button(ChiakiFee
return CHIAKI_ERR_SUCCESS;
}
+CHIAKI_EXPORT void chiaki_feedback_history_event_set_touchpad(ChiakiFeedbackHistoryEvent *event,
+ bool down, uint8_t pointer_id, uint16_t x, uint16_t y)
+{
+ event->len = 5;
+ event->buf[0] = down ? 0xd0 : 0xc0;
+ event->buf[1] = pointer_id & 0x7f;
+ event->buf[2] = (uint8_t)(x >> 4);
+ event->buf[3] = (uint8_t)((x & 0xf) << 4) | (uint8_t)(y >> 8);
+ event->buf[4] = (uint8_t)y;
+}
+
CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_history_buffer_init(ChiakiFeedbackHistoryBuffer *feedback_history_buffer, size_t size)
{
feedback_history_buffer->events = calloc(size, sizeof(ChiakiFeedbackHistoryEvent));
diff --git a/lib/src/feedbacksender.c b/lib/src/feedbacksender.c
index b812d39..cafffff 100644
--- a/lib/src/feedbacksender.c
+++ b/lib/src/feedbacksender.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -98,10 +83,24 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_sender_set_controller_state(Chiaki
static bool controller_state_equals_for_feedback_state(ChiakiControllerState *a, ChiakiControllerState *b)
{
- return a->left_x == b->left_x
+ if(!(a->left_x == b->left_x
&& a->left_y == b->left_y
&& a->right_x == b->right_x
- && a->right_y == b->right_y;
+ && a->right_y == b->right_y))
+ return false;
+#define CHECKF(n) if(a->n < b->n - 0.0000001f || a->n > b->n + 0.0000001f) return false
+ CHECKF(gyro_x);
+ CHECKF(gyro_y);
+ CHECKF(gyro_z);
+ CHECKF(accel_x);
+ CHECKF(accel_y);
+ CHECKF(accel_z);
+ CHECKF(orient_x);
+ CHECKF(orient_y);
+ CHECKF(orient_z);
+ CHECKF(orient_w);
+#undef CHECKF
+ return true;
}
static void feedback_sender_send_state(ChiakiFeedbackSender *feedback_sender)
@@ -111,6 +110,18 @@ static void feedback_sender_send_state(ChiakiFeedbackSender *feedback_sender)
state.left_y = feedback_sender->controller_state.left_y;
state.right_x = feedback_sender->controller_state.right_x;
state.right_y = feedback_sender->controller_state.right_y;
+ state.gyro_x = feedback_sender->controller_state.gyro_x;
+ state.gyro_y = feedback_sender->controller_state.gyro_y;
+ state.gyro_z = feedback_sender->controller_state.gyro_z;
+ state.accel_x = feedback_sender->controller_state.accel_x;
+ state.accel_y = feedback_sender->controller_state.accel_y;
+ state.accel_z = feedback_sender->controller_state.accel_z;
+
+ state.orient_x = feedback_sender->controller_state.orient_x;
+ state.orient_y = feedback_sender->controller_state.orient_y;
+ state.orient_z = feedback_sender->controller_state.orient_z;
+ state.orient_w = feedback_sender->controller_state.orient_w;
+
ChiakiErrorCode err = chiaki_takion_send_feedback_state(feedback_sender->takion, feedback_sender->state_seq_num++, &state);
if(err != CHIAKI_ERR_SUCCESS)
CHIAKI_LOGE(feedback_sender->log, "FeedbackSender failed to send Feedback State");
@@ -118,9 +129,19 @@ static void feedback_sender_send_state(ChiakiFeedbackSender *feedback_sender)
static bool controller_state_equals_for_feedback_history(ChiakiControllerState *a, ChiakiControllerState *b)
{
- return a->buttons == b->buttons
+ if(!(a->buttons == b->buttons
&& a->l2_state == b->l2_state
- && a->r2_state == b->r2_state;
+ && a->r2_state == b->r2_state))
+ return false;
+
+ for(size_t i=0; itouches[i].id != b->touches[i].id)
+ return false;
+ if(a->touches[i].id >= 0 && (a->touches[i].x != b->touches[i].x || a->touches[i].y != b->touches[i].y))
+ return false;
+ }
+ return true;
}
static void feedback_sender_send_history_packet(ChiakiFeedbackSender *feedback_sender)
@@ -189,6 +210,29 @@ static void feedback_sender_send_history(ChiakiFeedbackSender *feedback_sender)
else
CHIAKI_LOGE(feedback_sender->log, "Feedback Sender failed to format button history event for R2");
}
+
+ for(size_t i=0; itouches[i].id != state_now->touches[i].id && state_prev->touches[i].id >= 0)
+ {
+ ChiakiFeedbackHistoryEvent event;
+ chiaki_feedback_history_event_set_touchpad(&event, false, (uint8_t)state_prev->touches[i].id,
+ state_prev->touches[i].x, state_prev->touches[i].y);
+ chiaki_feedback_history_buffer_push(&feedback_sender->history_buf, &event);
+ feedback_sender_send_history_packet(feedback_sender);
+ }
+ else if(state_now->touches[i].id >= 0
+ && (state_prev->touches[i].id != state_now->touches[i].id
+ || state_prev->touches[i].x != state_now->touches[i].x
+ || state_prev->touches[i].y != state_now->touches[i].y))
+ {
+ ChiakiFeedbackHistoryEvent event;
+ chiaki_feedback_history_event_set_touchpad(&event, true, (uint8_t)state_now->touches[i].id,
+ state_now->touches[i].x, state_now->touches[i].y);
+ chiaki_feedback_history_buffer_push(&feedback_sender->history_buf, &event);
+ feedback_sender_send_history_packet(feedback_sender);
+ }
+ }
}
static bool state_cond_check(void *user)
@@ -242,4 +286,4 @@ static void *feedback_sender_thread_func(void *user)
chiaki_mutex_unlock(&feedback_sender->state_mutex);
return NULL;
-}
\ No newline at end of file
+}
diff --git a/lib/src/ffmpegdecoder.c b/lib/src/ffmpegdecoder.c
new file mode 100644
index 0000000..5dd5bf0
--- /dev/null
+++ b/lib/src/ffmpegdecoder.c
@@ -0,0 +1,216 @@
+
+#include
+
+#include
+
+static enum AVCodecID chiaki_codec_av_codec_id(ChiakiCodec codec)
+{
+ switch(codec)
+ {
+ case CHIAKI_CODEC_H265:
+ case CHIAKI_CODEC_H265_HDR:
+ return AV_CODEC_ID_H265;
+ default:
+ return AV_CODEC_ID_H264;
+ }
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_ffmpeg_decoder_init(ChiakiFfmpegDecoder *decoder, ChiakiLog *log,
+ ChiakiCodec codec, const char *hw_decoder_name,
+ ChiakiFfmpegFrameAvailable frame_available_cb, void *frame_available_cb_user)
+{
+ decoder->log = log;
+ decoder->frame_available_cb = frame_available_cb;
+ decoder->frame_available_cb_user = frame_available_cb_user;
+
+ ChiakiErrorCode err = chiaki_mutex_init(&decoder->mutex, false);
+ if(err != CHIAKI_ERR_SUCCESS)
+ return err;
+
+ decoder->hw_device_ctx = NULL;
+ decoder->hw_pix_fmt = AV_PIX_FMT_NONE;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+ avcodec_register_all();
+#endif
+ enum AVCodecID av_codec = chiaki_codec_av_codec_id(codec);
+ decoder->av_codec = avcodec_find_decoder(av_codec);
+ if(!decoder->av_codec)
+ {
+ CHIAKI_LOGE(log, "%s Codec not available", chiaki_codec_name(codec));
+ goto error_mutex;
+ }
+
+ decoder->codec_context = avcodec_alloc_context3(decoder->av_codec);
+ if(!decoder->codec_context)
+ {
+ CHIAKI_LOGE(log, "Failed to alloc codec context");
+ goto error_mutex;
+ }
+
+ if(hw_decoder_name)
+ {
+ CHIAKI_LOGI(log, "Using hardware decoder \"%s\"", hw_decoder_name);
+ enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hw_decoder_name);
+ if(type == AV_HWDEVICE_TYPE_NONE)
+ {
+ CHIAKI_LOGE(log, "Hardware decoder \"%s\" not found", hw_decoder_name);
+ goto error_codec_context;
+ }
+
+ for(int i = 0;; i++)
+ {
+ const AVCodecHWConfig *config = avcodec_get_hw_config(decoder->av_codec, i);
+ if(!config)
+ {
+ CHIAKI_LOGE(log, "avcodec_get_hw_config failed");
+ goto error_codec_context;
+ }
+ if(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == type)
+ {
+ decoder->hw_pix_fmt = config->pix_fmt;
+ break;
+ }
+ }
+
+ if(av_hwdevice_ctx_create(&decoder->hw_device_ctx, type, NULL, NULL, 0) < 0)
+ {
+ CHIAKI_LOGE(log, "Failed to create hwdevice context");
+ goto error_codec_context;
+ }
+ decoder->codec_context->hw_device_ctx = av_buffer_ref(decoder->hw_device_ctx);
+ }
+
+ if(avcodec_open2(decoder->codec_context, decoder->av_codec, NULL) < 0)
+ {
+ CHIAKI_LOGE(log, "Failed to open codec context");
+ goto error_codec_context;
+ }
+
+ return CHIAKI_ERR_SUCCESS;
+error_codec_context:
+ if(decoder->hw_device_ctx)
+ av_buffer_unref(&decoder->hw_device_ctx);
+ avcodec_free_context(&decoder->codec_context);
+error_mutex:
+ chiaki_mutex_fini(&decoder->mutex);
+ return CHIAKI_ERR_UNKNOWN;
+}
+
+CHIAKI_EXPORT void chiaki_ffmpeg_decoder_fini(ChiakiFfmpegDecoder *decoder)
+{
+ avcodec_close(decoder->codec_context);
+ avcodec_free_context(&decoder->codec_context);
+ if(decoder->hw_device_ctx)
+ av_buffer_unref(&decoder->hw_device_ctx);
+}
+
+CHIAKI_EXPORT bool chiaki_ffmpeg_decoder_video_sample_cb(uint8_t *buf, size_t buf_size, void *user)
+{
+ ChiakiFfmpegDecoder *decoder = user;
+
+ chiaki_mutex_lock(&decoder->mutex);
+ AVPacket packet;
+ av_init_packet(&packet);
+ packet.data = buf;
+ packet.size = buf_size;
+ int r;
+send_packet:
+ r = avcodec_send_packet(decoder->codec_context, &packet);
+ if(r != 0)
+ {
+ if(r == AVERROR(EAGAIN))
+ {
+ CHIAKI_LOGE(decoder->log, "AVCodec internal buffer is full removing frames before pushing");
+ AVFrame *frame = av_frame_alloc();
+ if(!frame)
+ {
+ CHIAKI_LOGE(decoder->log, "Failed to alloc AVFrame");
+ goto hell;
+ }
+ r = avcodec_receive_frame(decoder->codec_context, frame);
+ av_frame_free(&frame);
+ if(r != 0)
+ {
+ CHIAKI_LOGE(decoder->log, "Failed to pull frame");
+ goto hell;
+ }
+ goto send_packet;
+ }
+ else
+ {
+ char errbuf[128];
+ av_make_error_string(errbuf, sizeof(errbuf), r);
+ CHIAKI_LOGE(decoder->log, "Failed to push frame: %s", errbuf);
+ goto hell;
+ }
+ }
+ chiaki_mutex_unlock(&decoder->mutex);
+
+ decoder->frame_available_cb(decoder, decoder->frame_available_cb_user);
+ return true;
+hell:
+ chiaki_mutex_unlock(&decoder->mutex);
+ return false;
+}
+
+static AVFrame *pull_from_hw(ChiakiFfmpegDecoder *decoder, AVFrame *hw_frame)
+{
+ AVFrame *sw_frame = av_frame_alloc();
+ if(av_hwframe_transfer_data(sw_frame, hw_frame, 0) < 0)
+ {
+ CHIAKI_LOGE(decoder->log, "Failed to transfer frame from hardware");
+ av_frame_unref(sw_frame);
+ sw_frame = NULL;
+ }
+ av_frame_unref(hw_frame);
+ return sw_frame;
+}
+
+CHIAKI_EXPORT AVFrame *chiaki_ffmpeg_decoder_pull_frame(ChiakiFfmpegDecoder *decoder)
+{
+ chiaki_mutex_lock(&decoder->mutex);
+ // always try to pull as much as possible and return only the very last frame
+ AVFrame *frame_last = NULL;
+ AVFrame *frame = NULL;
+ while(true)
+ {
+ AVFrame *next_frame;
+ if(frame_last)
+ {
+ av_frame_unref(frame_last);
+ next_frame = frame_last;
+ }
+ else
+ {
+ next_frame = av_frame_alloc();
+ if(!next_frame)
+ break;
+ }
+ frame_last = frame;
+ frame = next_frame;
+ int r = avcodec_receive_frame(decoder->codec_context, frame);
+ if(!r)
+ frame = decoder->hw_device_ctx ? pull_from_hw(decoder, frame) : frame;
+ else
+ {
+ if(r != AVERROR(EAGAIN))
+ CHIAKI_LOGE(decoder->log, "Decoding with FFMPEG failed");
+ av_frame_free(&frame);
+ frame = frame_last;
+ break;
+ }
+ }
+ chiaki_mutex_unlock(&decoder->mutex);
+
+ return frame;
+}
+
+CHIAKI_EXPORT enum AVPixelFormat chiaki_ffmpeg_decoder_get_pixel_format(ChiakiFfmpegDecoder *decoder)
+{
+ // TODO: this is probably very wrong, especially for hdr
+ return decoder->hw_device_ctx
+ ? AV_PIX_FMT_NV12
+ : AV_PIX_FMT_YUV420P;
+}
+
diff --git a/lib/src/frameprocessor.c b/lib/src/frameprocessor.c
index 1f5bce9..bcee36b 100644
--- a/lib/src/frameprocessor.c
+++ b/lib/src/frameprocessor.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -28,24 +13,47 @@
#include
#endif
-#define UNIT_SLOTS_MAX 256
+CHIAKI_EXPORT void chiaki_stream_stats_reset(ChiakiStreamStats *stats)
+{
+ stats->frames = 0;
+ stats->bytes = 0;
+}
+CHIAKI_EXPORT void chiaki_stream_stats_frame(ChiakiStreamStats *stats, uint64_t size)
+{
+ stats->frames++;
+ stats->bytes += size;
+ //float br = (float)chiaki_stream_stats_bitrate(stats, 60) / 1000000.0f;
+ //CHIAKI_LOGD(NULL, "bitrate: %f", br);
+}
+
+CHIAKI_EXPORT uint64_t chiaki_stream_stats_bitrate(ChiakiStreamStats *stats, uint64_t framerate)
+{
+ return (stats->bytes * 8 * framerate) / stats->frames;
+}
+
+#define UNIT_SLOTS_MAX 256
struct chiaki_frame_unit_t
{
size_t data_size;
};
-
CHIAKI_EXPORT void chiaki_frame_processor_init(ChiakiFrameProcessor *frame_processor, ChiakiLog *log)
{
frame_processor->log = log;
frame_processor->frame_buf = NULL;
frame_processor->frame_buf_size = 0;
+ frame_processor->buf_size_per_unit = 0;
+ frame_processor->buf_stride_per_unit = 0;
frame_processor->units_source_expected = 0;
frame_processor->units_fec_expected = 0;
+ frame_processor->units_source_received = 0;
+ frame_processor->units_fec_received = 0;
frame_processor->unit_slots = NULL;
frame_processor->unit_slots_size = 0;
+ frame_processor->flushed = true;
+ chiaki_stream_stats_reset(&frame_processor->stream_stats);
}
CHIAKI_EXPORT void chiaki_frame_processor_fini(ChiakiFrameProcessor *frame_processor)
@@ -62,6 +70,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_alloc_frame(ChiakiFrameProc
return CHIAKI_ERR_INVALID_DATA;
}
+ frame_processor->flushed = false;
frame_processor->units_source_expected = packet->units_in_frame_total - packet->units_in_frame_fec;
frame_processor->units_fec_expected = packet->units_in_frame_fec;
if(frame_processor->units_fec_expected < 1)
@@ -77,6 +86,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_alloc_frame(ChiakiFrameProc
}
frame_processor->buf_size_per_unit += ntohs(((chiaki_unaligned_uint16_t *)packet->data)[0]);
}
+ frame_processor->buf_stride_per_unit = ((frame_processor->buf_size_per_unit + 0xf) / 0x10) * 0x10;
if(frame_processor->buf_size_per_unit == 0)
{
@@ -116,9 +126,9 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_alloc_frame(ChiakiFrameProc
}
memset(frame_processor->unit_slots, 0, frame_processor->unit_slots_size * sizeof(ChiakiFrameUnit));
- if(frame_processor->unit_slots_size > SIZE_MAX / frame_processor->buf_size_per_unit)
+ if(frame_processor->unit_slots_size > SIZE_MAX / frame_processor->buf_stride_per_unit)
return CHIAKI_ERR_OVERFLOW;
- size_t frame_buf_size_required = frame_processor->unit_slots_size * frame_processor->buf_size_per_unit;
+ size_t frame_buf_size_required = frame_processor->unit_slots_size * frame_processor->buf_stride_per_unit;
if(frame_processor->frame_buf_size < frame_buf_size_required)
{
free(frame_processor->frame_buf);
@@ -142,7 +152,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_put_unit(ChiakiFrameProcess
CHIAKI_LOGE(frame_processor->log, "Packet's unit index is too high");
return CHIAKI_ERR_INVALID_DATA;
}
-
+
if(!packet->data_size)
{
CHIAKI_LOGW(frame_processor->log, "Unit is empty");
@@ -154,7 +164,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_put_unit(ChiakiFrameProcess
CHIAKI_LOGW(frame_processor->log, "Unit is bigger than pre-calculated size!");
return CHIAKI_ERR_INVALID_DATA;
}
-
+
ChiakiFrameUnit *unit = frame_processor->unit_slots + packet->unit_index;
if(unit->data_size)
{
@@ -163,9 +173,12 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_put_unit(ChiakiFrameProcess
}
unit->data_size = packet->data_size;
- memcpy(frame_processor->frame_buf + packet->unit_index * frame_processor->buf_size_per_unit,
- packet->data,
- packet->data_size);
+ if(!frame_processor->flushed)
+ {
+ memcpy(frame_processor->frame_buf + packet->unit_index * frame_processor->buf_stride_per_unit,
+ packet->data,
+ packet->data_size);
+ }
if(packet->unit_index < frame_processor->units_source_expected)
frame_processor->units_source_received++;
@@ -175,6 +188,13 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_frame_processor_put_unit(ChiakiFrameProcess
return CHIAKI_ERR_SUCCESS;
}
+CHIAKI_EXPORT void chiaki_frame_processor_report_packet_stats(ChiakiFrameProcessor *frame_processor, ChiakiPacketStats *packet_stats)
+{
+ uint64_t received = frame_processor->units_source_received + frame_processor->units_fec_received;
+ uint64_t expected = frame_processor->units_source_expected + frame_processor->units_fec_expected;
+ chiaki_packet_stats_push_generation(packet_stats, received, expected - received);
+}
+
static ChiakiErrorCode chiaki_frame_processor_fec(ChiakiFrameProcessor *frame_processor)
{
CHIAKI_LOGI(frame_processor->log, "Frame Processor received %u+%u / %u+%u units, attempting FEC",
@@ -206,8 +226,9 @@ static ChiakiErrorCode chiaki_frame_processor_fec(ChiakiFrameProcessor *frame_pr
}
assert(erasure_index == erasures_count);
- ChiakiErrorCode err = chiaki_fec_decode(frame_processor->frame_buf, frame_processor->buf_size_per_unit,
- frame_processor->units_source_expected, frame_processor->units_fec_received,
+ ChiakiErrorCode err = chiaki_fec_decode(frame_processor->frame_buf,
+ frame_processor->buf_size_per_unit, frame_processor->buf_stride_per_unit,
+ frame_processor->units_source_expected, frame_processor->units_fec_expected,
erasures, erasures_count);
if(err != CHIAKI_ERR_SUCCESS)
@@ -224,7 +245,7 @@ static ChiakiErrorCode chiaki_frame_processor_fec(ChiakiFrameProcessor *frame_pr
for(size_t i=0; iunits_source_expected; i++)
{
ChiakiFrameUnit *slot = frame_processor->unit_slots + i;
- uint8_t *buf_ptr = frame_processor->frame_buf + frame_processor->buf_size_per_unit * i;
+ uint8_t *buf_ptr = frame_processor->frame_buf + frame_processor->buf_stride_per_unit * i;
uint16_t padding = ntohs(*((chiaki_unaligned_uint16_t *)buf_ptr));
if(padding >= frame_processor->buf_size_per_unit)
{
@@ -243,9 +264,13 @@ static ChiakiErrorCode chiaki_frame_processor_fec(ChiakiFrameProcessor *frame_pr
CHIAKI_EXPORT ChiakiFrameProcessorFlushResult chiaki_frame_processor_flush(ChiakiFrameProcessor *frame_processor, uint8_t **frame, size_t *frame_size)
{
- if(frame_processor->units_source_expected == 0)
+ if(frame_processor->units_source_expected == 0 || frame_processor->flushed)
return CHIAKI_FRAME_PROCESSOR_FLUSH_RESULT_FAILED;
+ //CHIAKI_LOGD(NULL, "source: %u, fec: %u",
+ // frame_processor->units_source_expected,
+ // frame_processor->units_fec_expected);
+
ChiakiFrameProcessorFlushResult result = CHIAKI_FRAME_PROCESSOR_FLUSH_RESULT_SUCCESS;
if(frame_processor->units_source_received < frame_processor->units_source_expected)
{
@@ -272,11 +297,13 @@ CHIAKI_EXPORT ChiakiFrameProcessorFlushResult chiaki_frame_processor_flush(Chiak
continue;
}
size_t part_size = unit->data_size - 2;
- uint8_t *buf_ptr = frame_processor->frame_buf + i*frame_processor->buf_size_per_unit;
+ uint8_t *buf_ptr = frame_processor->frame_buf + i*frame_processor->buf_stride_per_unit;
memmove(frame_processor->frame_buf + cur, buf_ptr + 2, part_size);
cur += part_size;
}
+ chiaki_stream_stats_frame(&frame_processor->stream_stats, (uint64_t)cur);
+
*frame = frame_processor->frame_buf;
*frame_size = cur;
return result;
diff --git a/lib/src/gkcrypt.c b/lib/src/gkcrypt.c
index f8be2c1..e8dcb12 100644
--- a/lib/src/gkcrypt.c
+++ b/lib/src/gkcrypt.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -21,16 +6,21 @@
#include
#include
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+#include "mbedtls/aes.h"
+#include "mbedtls/md.h"
+#include "mbedtls/gcm.h"
+#include "mbedtls/sha256.h"
+#else
#include
#include
#include
+#endif
#include "utils.h"
-
#define KEY_BUF_CHUNK_SIZE 0x1000
-
static ChiakiErrorCode gkcrypt_gen_key_iv(ChiakiGKCrypt *gkcrypt, uint8_t index, const uint8_t *handshake_key, const uint8_t *ecdh_secret);
static void *gkcrypt_thread_func(void *user);
@@ -69,7 +59,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_init(ChiakiGKCrypt *gkcrypt, Chiaki
{
gkcrypt->key_buf = NULL;
}
-
err = gkcrypt_gen_key_iv(gkcrypt, index, handshake_key, ecdh_secret);
if(err != CHIAKI_ERR_SUCCESS)
{
@@ -119,7 +108,6 @@ CHIAKI_EXPORT void chiaki_gkcrypt_fini(ChiakiGKCrypt *gkcrypt)
}
}
-
static ChiakiErrorCode gkcrypt_gen_key_iv(ChiakiGKCrypt *gkcrypt, uint8_t index, const uint8_t *handshake_key, const uint8_t *ecdh_secret)
{
uint8_t data[3 + CHIAKI_HANDSHAKE_KEY_SIZE + 2];
@@ -132,9 +120,41 @@ static ChiakiErrorCode gkcrypt_gen_key_iv(ChiakiGKCrypt *gkcrypt, uint8_t index,
uint8_t hmac[CHIAKI_GKCRYPT_BLOCK_SIZE*2];
size_t hmac_size = sizeof(hmac);
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ mbedtls_md_context_t ctx;
+ mbedtls_md_init(&ctx);
+
+ if(mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) != 0)
+ {
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ if(mbedtls_md_hmac_starts(&ctx, ecdh_secret, CHIAKI_ECDH_SECRET_SIZE) != 0)
+ {
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ if(mbedtls_md_hmac_update(&ctx, data, sizeof(data)) != 0)
+ {
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ if(mbedtls_md_hmac_finish(&ctx, hmac) != 0)
+ {
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ mbedtls_md_free(&ctx);
+
+#else
if(!HMAC(EVP_sha256(), ecdh_secret, CHIAKI_ECDH_SECRET_SIZE, data, sizeof(data), hmac, (unsigned int *)&hmac_size))
return CHIAKI_ERR_UNKNOWN;
+#endif
assert(hmac_size == sizeof(hmac));
memcpy(gkcrypt->key_base, hmac, CHIAKI_GKCRYPT_BLOCK_SIZE);
@@ -164,7 +184,14 @@ CHIAKI_EXPORT void chiaki_gkcrypt_gen_gmac_key(uint64_t index, const uint8_t *ke
memcpy(data, key_base, 0x10);
counter_add(data + 0x10, iv, index * CHIAKI_GKCRYPT_GMAC_KEY_REFRESH_IV_OFFSET);
uint8_t md[0x20];
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ // last param
+ // is224 Determines which function to use.
+ // This must be either 0 for SHA-256, or 1 for SHA-224.
+ mbedtls_sha256_ret(data, sizeof(data), md, 0);
+#else
SHA256(data, 0x20, md);
+#endif
xor_bytes(md, md + 0x10, 0x10);
memcpy(key_out, md, CHIAKI_GKCRYPT_BLOCK_SIZE);
}
@@ -184,11 +211,23 @@ CHIAKI_EXPORT void chiaki_gkcrypt_gen_tmp_gmac_key(ChiakiGKCrypt *gkcrypt, uint6
memcpy(key_out, gkcrypt->key_gmac_base, sizeof(gkcrypt->key_gmac_base));
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gen_key_stream(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gen_key_stream(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size)
{
assert(key_pos % CHIAKI_GKCRYPT_BLOCK_SIZE == 0);
assert(buf_size % CHIAKI_GKCRYPT_BLOCK_SIZE == 0);
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ // build mbedtls aes context
+ mbedtls_aes_context ctx;
+ mbedtls_aes_init(&ctx);
+
+ if(mbedtls_aes_setkey_enc(&ctx, gkcrypt->key_base, 128) != 0)
+ {
+ mbedtls_aes_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+#else
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if(!ctx)
return CHIAKI_ERR_UNKNOWN;
@@ -204,12 +243,25 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gen_key_stream(ChiakiGKCrypt *gkcry
EVP_CIPHER_CTX_free(ctx);
return CHIAKI_ERR_UNKNOWN;
}
-
+#endif
int counter_offset = (int)(key_pos / CHIAKI_GKCRYPT_BLOCK_SIZE);
for(uint8_t *cur = buf, *end = buf + buf_size; cur < end; cur += CHIAKI_GKCRYPT_BLOCK_SIZE)
counter_add(cur, gkcrypt->iv, counter_offset++);
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ for(int i = 0; i < buf_size; i = i + 16)
+ {
+ // loop over all blocks of 16 bytes (128 bits)
+ if(mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, buf + i, buf + i) != 0)
+ {
+ mbedtls_aes_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+ }
+
+ mbedtls_aes_free(&ctx);
+#else
int outl;
EVP_EncryptUpdate(ctx, buf, &outl, buf, (int)buf_size);
if(outl != buf_size)
@@ -219,6 +271,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gen_key_stream(ChiakiGKCrypt *gkcry
}
EVP_CIPHER_CTX_free(ctx);
+#endif
return CHIAKI_ERR_SUCCESS;
}
@@ -227,7 +280,7 @@ static bool gkcrypt_key_buf_should_generate(ChiakiGKCrypt *gkcrypt)
return gkcrypt->last_key_pos > gkcrypt->key_buf_key_pos_min + gkcrypt->key_buf_populated / 2;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_get_key_stream(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_get_key_stream(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size)
{
if(!gkcrypt->key_buf)
return chiaki_gkcrypt_gen_key_stream(gkcrypt, key_pos, buf, buf_size);
@@ -242,7 +295,15 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_get_key_stream(ChiakiGKCrypt *gkcry
if(key_pos < gkcrypt->key_buf_key_pos_min
|| key_pos + buf_size >= gkcrypt->key_buf_key_pos_min + gkcrypt->key_buf_populated)
{
- CHIAKI_LOGW(gkcrypt->log, "Requested key stream for key pos %#llx on GKCrypt %d, but it's not in the buffer", (int)key_pos, gkcrypt->index);
+ CHIAKI_LOGW(gkcrypt->log, "Requested key stream for key pos %#llx on GKCrypt %d, but it's not in the buffer:"
+ " key buf size %#llx, start offset: %#llx, populated: %#llx, min key pos: %#llx, last key pos: %#llx",
+ (unsigned long long)key_pos,
+ gkcrypt->index,
+ (unsigned long long)gkcrypt->key_buf_size,
+ (unsigned long long)gkcrypt->key_buf_start_offset,
+ (unsigned long long)gkcrypt->key_buf_populated,
+ (unsigned long long)gkcrypt->key_buf_key_pos_min,
+ (unsigned long long)gkcrypt->last_key_pos);
chiaki_mutex_unlock(&gkcrypt->key_buf_mutex);
err = chiaki_gkcrypt_gen_key_stream(gkcrypt, key_pos, buf, buf_size);
}
@@ -269,9 +330,9 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_get_key_stream(ChiakiGKCrypt *gkcry
return err;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_decrypt(ChiakiGKCrypt *gkcrypt, size_t key_pos, uint8_t *buf, size_t buf_size)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_decrypt(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, uint8_t *buf, size_t buf_size)
{
- size_t padding_pre = key_pos % CHIAKI_GKCRYPT_BLOCK_SIZE;
+ uint64_t padding_pre = key_pos % CHIAKI_GKCRYPT_BLOCK_SIZE;
size_t full_size = ((padding_pre + buf_size + CHIAKI_GKCRYPT_BLOCK_SIZE - 1) / CHIAKI_GKCRYPT_BLOCK_SIZE) * CHIAKI_GKCRYPT_BLOCK_SIZE;
uint8_t *key_stream = malloc(full_size);
@@ -291,7 +352,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_decrypt(ChiakiGKCrypt *gkcrypt, siz
return CHIAKI_ERR_SUCCESS;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gmac(ChiakiGKCrypt *gkcrypt, size_t key_pos, const uint8_t *buf, size_t buf_size, uint8_t *gmac_out)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gmac(ChiakiGKCrypt *gkcrypt, uint64_t key_pos, const uint8_t *buf, size_t buf_size, uint8_t *gmac_out)
{
uint8_t iv[CHIAKI_GKCRYPT_BLOCK_SIZE];
counter_add(iv, gkcrypt->iv, key_pos / 0x10);
@@ -310,6 +371,41 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_gkcrypt_gmac(ChiakiGKCrypt *gkcrypt, size_t
gmac_key = gmac_key_tmp;
}
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ // build mbedtls gcm context AES_128_GCM
+ // Encryption
+ mbedtls_gcm_context actx;
+ mbedtls_gcm_init(&actx);
+ // set gmac_key 128 bits key
+ if(mbedtls_gcm_setkey(&actx, MBEDTLS_CIPHER_ID_AES, gmac_key, CHIAKI_GKCRYPT_BLOCK_SIZE * 8) != 0)
+ {
+ mbedtls_gcm_free(&actx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ // encrypt without additional data
+ if(mbedtls_gcm_starts(&actx, MBEDTLS_GCM_ENCRYPT, iv, CHIAKI_GKCRYPT_BLOCK_SIZE, NULL, 0) != 0)
+ {
+ mbedtls_gcm_free(&actx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+ // set "additional data" only whitout input nor output
+ // to get the same result as:
+ // EVP_EncryptUpdate(ctx, NULL, &len, buf, (int)buf_size)
+ if(mbedtls_gcm_crypt_and_tag(&actx, MBEDTLS_GCM_ENCRYPT,
+ 0, iv, CHIAKI_GKCRYPT_BLOCK_SIZE,
+ buf, buf_size, NULL, NULL,
+ CHIAKI_GKCRYPT_GMAC_SIZE, gmac_out) != 0)
+ {
+
+ mbedtls_gcm_free(&actx);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ mbedtls_gcm_free(&actx);
+
+ return CHIAKI_ERR_SUCCESS;
+#else
ChiakiErrorCode ret = CHIAKI_ERR_SUCCESS;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
@@ -360,6 +456,7 @@ fail_cipher:
EVP_CIPHER_CTX_free(ctx);
fail:
return ret;
+#endif
}
static bool key_buf_mutex_pred(void *user)
@@ -381,7 +478,7 @@ static ChiakiErrorCode gkcrypt_generate_next_chunk(ChiakiGKCrypt *gkcrypt)
{
assert(gkcrypt->key_buf_populated + KEY_BUF_CHUNK_SIZE <= gkcrypt->key_buf_size);
size_t buf_offset = (gkcrypt->key_buf_start_offset + gkcrypt->key_buf_populated) % gkcrypt->key_buf_size;
- size_t key_pos = gkcrypt->key_buf_key_pos_min + gkcrypt->key_buf_populated;
+ uint64_t key_pos = gkcrypt->key_buf_key_pos_min + gkcrypt->key_buf_populated;
uint8_t *buf_start = gkcrypt->key_buf + buf_offset;
chiaki_mutex_unlock(&gkcrypt->key_buf_mutex);
@@ -401,15 +498,14 @@ static ChiakiErrorCode gkcrypt_generate_next_chunk(ChiakiGKCrypt *gkcrypt)
static void *gkcrypt_thread_func(void *user)
{
ChiakiGKCrypt *gkcrypt = user;
-
CHIAKI_LOGV(gkcrypt->log, "GKCrypt %d thread starting", (int)gkcrypt->index);
ChiakiErrorCode err = chiaki_mutex_lock(&gkcrypt->key_buf_mutex);
assert(err == CHIAKI_ERR_SUCCESS);
-
while(1)
{
err = chiaki_cond_wait_pred(&gkcrypt->key_buf_cond, &gkcrypt->key_buf_mutex, key_buf_mutex_pred, gkcrypt);
+
if(gkcrypt->key_buf_thread_stop || err != CHIAKI_ERR_SUCCESS)
break;
@@ -424,7 +520,7 @@ static void *gkcrypt_thread_func(void *user)
if(gkcrypt->last_key_pos > gkcrypt->key_buf_key_pos_min + gkcrypt->key_buf_populated)
{
// skip ahead if the last key pos is already beyond our buffer
- size_t key_pos = (gkcrypt->last_key_pos / KEY_BUF_CHUNK_SIZE) * KEY_BUF_CHUNK_SIZE;
+ uint64_t key_pos = (gkcrypt->last_key_pos / KEY_BUF_CHUNK_SIZE) * KEY_BUF_CHUNK_SIZE;
CHIAKI_LOGW(gkcrypt->log, "Already requested a higher key pos than in the buffer, skipping ahead from min %#llx to %#llx",
(unsigned long long)gkcrypt->key_buf_key_pos_min,
(unsigned long long)key_pos);
@@ -438,7 +534,6 @@ static void *gkcrypt_thread_func(void *user)
gkcrypt->key_buf_key_pos_min += KEY_BUF_CHUNK_SIZE;
gkcrypt->key_buf_populated -= KEY_BUF_CHUNK_SIZE;
}
-
err = gkcrypt_generate_next_chunk(gkcrypt);
if(err != CHIAKI_ERR_SUCCESS)
break;
@@ -447,3 +542,27 @@ static void *gkcrypt_thread_func(void *user)
chiaki_mutex_unlock(&gkcrypt->key_buf_mutex);
return NULL;
}
+
+CHIAKI_EXPORT void chiaki_key_state_init(ChiakiKeyState *state)
+{
+ state->prev = 0;
+}
+
+CHIAKI_EXPORT uint64_t chiaki_key_state_request_pos(ChiakiKeyState *state, uint32_t low, bool commit)
+{
+ uint32_t prev_low = (uint32_t)state->prev;
+ uint32_t high = (uint32_t)(state->prev >> 32);
+ if(chiaki_seq_num_32_gt(low, prev_low) && low < prev_low)
+ high++;
+ else if(chiaki_seq_num_32_lt(low, prev_low) && low > prev_low && high)
+ high--;
+ uint64_t res = (((uint64_t)high) << 32) | ((uint64_t)low);
+ if(commit)
+ state->prev = res;
+ return res;
+}
+
+CHIAKI_EXPORT void chiaki_key_state_commit(ChiakiKeyState *state, uint64_t prev)
+{
+ state->prev = prev;
+}
diff --git a/lib/src/http.c b/lib/src/http.c
index 50c86fe..5c07802 100644
--- a/lib/src/http.c
+++ b/lib/src/http.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
@@ -26,7 +11,6 @@
#include
#endif
-
CHIAKI_EXPORT void chiaki_http_header_free(ChiakiHttpHeader *header)
{
while(header)
@@ -162,7 +146,15 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_recv_http_header(int sock, char *buf, size_
return err;
}
- int received = (int)recv(sock, buf, (int)buf_size, 0);
+ int received;
+ do
+ {
+ received = (int)recv(sock, buf, (int)buf_size, 0);
+#if _WIN32
+ } while(false);
+#else
+ } while(received < 0 && errno == EINTR);
+#endif
if(received <= 0)
return received == 0 ? CHIAKI_ERR_DISCONNECTED : CHIAKI_ERR_NETWORK;
diff --git a/lib/src/launchspec.c b/lib/src/launchspec.c
index 438ae9d..abed382 100644
--- a/lib/src/launchspec.c
+++ b/lib/src/launchspec.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -65,7 +50,8 @@ static const char launchspec_fmt[] =
"\"connectedControllers\":[\"xinput\",\"ds3\",\"ds4\"],"
"\"yuvCoefficient\":\"bt601\","
"\"videoEncoderProfile\":\"hw4.1\","
- "\"audioEncoderProfile\":\"audio1\""
+ "\"audioEncoderProfile\":\"audio1\"" // 6
+ "%s"
"},"
"\"userProfile\":{"
"\"onlineId\":\"psnId\","
@@ -73,7 +59,9 @@ static const char launchspec_fmt[] =
"\"region\":\"US\","
"\"languagesUsed\":[\"en\",\"jp\"]"
"},"
- "\"handshakeKey\":\"%s\"" // 6
+ "%s" // 7
+ "%s" // 8
+ "\"handshakeKey\":\"%s\"" // 9
"}";
CHIAKI_EXPORT int chiaki_launchspec_format(char *buf, size_t buf_size, ChiakiLaunchSpec *launch_spec)
@@ -83,10 +71,25 @@ CHIAKI_EXPORT int chiaki_launchspec_format(char *buf, size_t buf_size, ChiakiLau
if(err != CHIAKI_ERR_SUCCESS)
return -1;
+ char *extras[3];
+ if(chiaki_target_is_ps5(launch_spec->target)) // TODO: probably also for ps4, but only 12
+ {
+ extras[0] = ",\"adaptiveStreamMode\": \"resize\"";
+ extras[1] = chiaki_codec_is_h265(launch_spec->codec)
+ ? "\"videoCodec\":\"hevc\","
+ : "\"videoCodec\":\"avc\",";
+ extras[2] = chiaki_codec_is_hdr(launch_spec->codec)
+ ? "\"dynamicRange\":\"HDR\","
+ : "\"dynamicRange\":\"SDR\",";
+ }
+ else
+ extras[0] = extras[1] = extras[2] = "";
+
int written = snprintf(buf, buf_size, launchspec_fmt,
launch_spec->width, launch_spec->height, launch_spec->max_fps,
- launch_spec->bw_kbps_sent, launch_spec->mtu, launch_spec->rtt, handshake_key_b64);
+ launch_spec->bw_kbps_sent, launch_spec->mtu, launch_spec->rtt,
+ extras[0], extras[1], extras[2], handshake_key_b64);
if(written < 0 || written >= buf_size)
return -1;
return written;
-}
\ No newline at end of file
+}
diff --git a/lib/src/log.c b/lib/src/log.c
index 1055be1..e0ba095 100644
--- a/lib/src/log.c
+++ b/lib/src/log.c
@@ -1,24 +1,10 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
#include
+#include
CHIAKI_EXPORT char chiaki_log_level_char(ChiakiLogLevel level)
{
@@ -191,4 +177,51 @@ CHIAKI_EXPORT void chiaki_log_hexdump_raw(ChiakiLog *log, ChiakiLogLevel level,
str[buf_size*2] = 0;
chiaki_log(log, level, "%s", str);
free(str);
-}
\ No newline at end of file
+}
+
+static void log_sniffer_cb(ChiakiLogLevel level, const char *msg, void *user);
+
+CHIAKI_EXPORT void chiaki_log_sniffer_init(ChiakiLogSniffer *sniffer, uint32_t level_mask, ChiakiLog *forward_log)
+{
+ sniffer->forward_log = forward_log;
+ // level_mask is applied later and everything is forwarded unmasked, so use ALL here:
+ chiaki_log_init(&sniffer->sniff_log, CHIAKI_LOG_ALL, log_sniffer_cb, sniffer);
+ sniffer->sniff_level_mask = level_mask;
+ sniffer->buf = calloc(1, 1);
+ sniffer->buf_len = '\0';
+}
+
+CHIAKI_EXPORT void chiaki_log_sniffer_fini(ChiakiLogSniffer *sniffer)
+{
+ free(sniffer->buf);
+}
+
+static void log_sniffer_push(ChiakiLogSniffer *sniffer, ChiakiLogLevel level, const char *msg)
+{
+ size_t len = strlen(msg);
+ if(!len)
+ return;
+ bool nl = sniffer->buf_len != 0;
+ char *new_buf = realloc(sniffer->buf, sniffer->buf_len + (nl ? 1 : 0) + 4 + len + 1);
+ if(!new_buf)
+ return;
+ sniffer->buf = new_buf;
+ if(nl)
+ sniffer->buf[sniffer->buf_len++] = '\n';
+ sniffer->buf[sniffer->buf_len++] = '[';
+ sniffer->buf[sniffer->buf_len++] = chiaki_log_level_char(level);
+ sniffer->buf[sniffer->buf_len++] = ']';
+ sniffer->buf[sniffer->buf_len++] = ' ';
+ memcpy(sniffer->buf + sniffer->buf_len, msg, len);
+ sniffer->buf_len += len;
+ sniffer->buf[sniffer->buf_len] = '\0';
+}
+
+static void log_sniffer_cb(ChiakiLogLevel level, const char *msg, void *user)
+{
+ ChiakiLogSniffer *sniffer = user;
+ if(level & sniffer->sniff_level_mask)
+ log_sniffer_push(sniffer, level, msg);
+ if(sniffer->forward_log)
+ chiaki_log(sniffer->forward_log, level, "%s", msg);
+}
diff --git a/lib/src/opusdecoder.c b/lib/src/opusdecoder.c
index ccfa2b0..31eaf9c 100644
--- a/lib/src/opusdecoder.c
+++ b/lib/src/opusdecoder.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#if CHIAKI_LIB_ENABLE_OPUS
diff --git a/lib/src/orientation.c b/lib/src/orientation.c
new file mode 100644
index 0000000..7f07b09
--- /dev/null
+++ b/lib/src/orientation.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include
+#include
+
+#define SIN_1_4_PI 0.7071067811865475
+#define SIN_NEG_1_4_PI -0.7071067811865475
+#define COS_1_4_PI 0.7071067811865476
+#define COS_NEG_1_4_PI 0.7071067811865476
+
+#define WARMUP_SAMPLES_COUNT 30
+#define BETA_WARMUP 20.0f
+#define BETA_DEFAULT 0.05f
+
+CHIAKI_EXPORT void chiaki_orientation_init(ChiakiOrientation *orient)
+{
+ // 90 deg rotation around x for Madgwick
+ orient->x = SIN_1_4_PI;
+ orient->y = 0.0f;
+ orient->z = 0.0f;
+ orient->w = COS_1_4_PI;
+}
+
+static float inv_sqrt(float x);
+
+CHIAKI_EXPORT void chiaki_orientation_update(ChiakiOrientation *orient,
+ float gx, float gy, float gz, float ax, float ay, float az, float beta, float time_step_sec)
+{
+ float q0 = orient->w, q1 = orient->x, q2 = orient->y, q3 = orient->z;
+ // Madgwick's IMU algorithm.
+ // See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
+ float recip_norm;
+ float s0, s1, s2, s3;
+ float q_dot1, q_dot2, q_dot3, q_dot4;
+ float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3;
+
+ // Rate of change of quaternion from gyroscope
+ q_dot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
+ q_dot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy);
+ q_dot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx);
+ q_dot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx);
+
+ // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
+ if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f)))
+ {
+ // Normalise accelerometer measurement
+ recip_norm = inv_sqrt(ax * ax + ay * ay + az * az);
+ ax *= recip_norm;
+ ay *= recip_norm;
+ az *= recip_norm;
+
+ // Auxiliary variables to avoid repeated arithmetic
+ _2q0 = 2.0f * q0;
+ _2q1 = 2.0f * q1;
+ _2q2 = 2.0f * q2;
+ _2q3 = 2.0f * q3;
+ _4q0 = 4.0f * q0;
+ _4q1 = 4.0f * q1;
+ _4q2 = 4.0f * q2;
+ _8q1 = 8.0f * q1;
+ _8q2 = 8.0f * q2;
+ q0q0 = q0 * q0;
+ q1q1 = q1 * q1;
+ q2q2 = q2 * q2;
+ q3q3 = q3 * q3;
+
+ // Gradient decent algorithm corrective step
+ s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 - _2q1 * ay;
+ s1 = _4q1 * q3q3 - _2q3 * ax + 4.0f * q0q0 * q1 - _2q0 * ay - _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az;
+ s2 = 4.0f * q0q0 * q2 + _2q0 * ax + _4q2 * q3q3 - _2q3 * ay - _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az;
+ s3 = 4.0f * q1q1 * q3 - _2q1 * ax + 4.0f * q2q2 * q3 - _2q2 * ay;
+ recip_norm = s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3; // normalise step magnitude
+ // avoid NaN when the orientation is already perfect or inverse to perfect
+ if(recip_norm > 0.000001f)
+ {
+ recip_norm = inv_sqrt(recip_norm);
+ s0 *= recip_norm;
+ s1 *= recip_norm;
+ s2 *= recip_norm;
+ s3 *= recip_norm;
+
+ // Apply feedback step
+ q_dot1 -= beta * s0;
+ q_dot2 -= beta * s1;
+ q_dot3 -= beta * s2;
+ q_dot4 -= beta * s3;
+ }
+ }
+
+ // Integrate rate of change of quaternion to yield quaternion
+ q0 += q_dot1 * time_step_sec;
+ q1 += q_dot2 * time_step_sec;
+ q2 += q_dot3 * time_step_sec;
+ q3 += q_dot4 * time_step_sec;
+
+ // Normalise quaternion
+ recip_norm = inv_sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
+ q0 *= recip_norm;
+ q1 *= recip_norm;
+ q2 *= recip_norm;
+ q3 *= recip_norm;
+
+ orient->x = q1;
+ orient->y = q2;
+ orient->z = q3;
+ orient->w = q0;
+}
+
+static float inv_sqrt(float x)
+{
+#if 1
+ return 1.0f / sqrt(x);
+#else
+ // Fast inverse square-root
+ // See: http://en.wikipedia.org/wiki/Fast_inverse_square_root
+ float halfx = 0.5f * x;
+ float y = x;
+ long i = *(long*)&y;
+ i = 0x5f3759df - (i>>1);
+ y = *(float*)&i;
+ y = y * (1.5f - (halfx * y * y));
+ return y;
+#endif
+}
+
+CHIAKI_EXPORT void chiaki_orientation_tracker_init(ChiakiOrientationTracker *tracker)
+{
+ tracker->accel_x = 0.0f;
+ tracker->accel_y = 1.0f;
+ tracker->accel_z = 0.0f;
+ tracker->gyro_x = tracker->gyro_y = tracker->gyro_z = 0.0f;
+ chiaki_orientation_init(&tracker->orient);
+ tracker->timestamp = 0;
+ tracker->sample_index = 0;
+}
+
+CHIAKI_EXPORT void chiaki_orientation_tracker_update(ChiakiOrientationTracker *tracker,
+ float gx, float gy, float gz, float ax, float ay, float az, uint32_t timestamp_us)
+{
+ tracker->gyro_x = gx;
+ tracker->gyro_y = gy;
+ tracker->gyro_z = gz;
+ tracker->accel_x = ax;
+ tracker->accel_y = ay;
+ tracker->accel_z = az;
+ tracker->sample_index++;
+ if(tracker->sample_index <= 1)
+ {
+ tracker->timestamp = timestamp_us;
+ return;
+ }
+ uint64_t delta_us = timestamp_us;
+ if(delta_us < tracker->timestamp)
+ delta_us += (1ULL << 32);
+ delta_us -= tracker->timestamp;
+ tracker->timestamp = timestamp_us;
+ chiaki_orientation_update(&tracker->orient, gx, gy, gz, ax, ay, az,
+ tracker->sample_index < WARMUP_SAMPLES_COUNT ? BETA_WARMUP : BETA_DEFAULT,
+ (float)delta_us / 1000000.0f);
+}
+
+CHIAKI_EXPORT void chiaki_orientation_tracker_apply_to_controller_state(ChiakiOrientationTracker *tracker,
+ ChiakiControllerState *state)
+{
+ state->gyro_x = tracker->gyro_x;
+ state->gyro_y = tracker->gyro_y;
+ state->gyro_z = tracker->gyro_z;
+ state->accel_x = tracker->accel_x;
+ state->accel_y = tracker->accel_y;
+ state->accel_z = tracker->accel_z;
+ // -90 deg rotation around x from Madgwick
+ state->orient_w = COS_NEG_1_4_PI * tracker->orient.w - SIN_NEG_1_4_PI * tracker->orient.x;
+ state->orient_x = COS_NEG_1_4_PI * tracker->orient.x + SIN_NEG_1_4_PI * tracker->orient.w;
+ state->orient_y = COS_NEG_1_4_PI * tracker->orient.y - SIN_NEG_1_4_PI * tracker->orient.z;
+ state->orient_z = COS_NEG_1_4_PI * tracker->orient.z + SIN_NEG_1_4_PI * tracker->orient.y;
+}
diff --git a/lib/src/packetstats.c b/lib/src/packetstats.c
new file mode 100644
index 0000000..c7da9d6
--- /dev/null
+++ b/lib/src/packetstats.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include
+#include
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_packet_stats_init(ChiakiPacketStats *stats)
+{
+ stats->gen_received = 0;
+ stats->gen_lost = 0;
+ stats->seq_min = 0;
+ stats->seq_max = 0;
+ stats->seq_received = 0;
+ return chiaki_mutex_init(&stats->mutex, false);
+}
+
+CHIAKI_EXPORT void chiaki_packet_stats_fini(ChiakiPacketStats *stats)
+{
+ chiaki_mutex_fini(&stats->mutex);
+}
+
+static void reset_stats(ChiakiPacketStats *stats)
+{
+ stats->gen_received = 0;
+ stats->gen_lost = 0;
+ stats->seq_min = stats->seq_max;
+ stats->seq_received = 0;
+}
+
+CHIAKI_EXPORT void chiaki_packet_stats_reset(ChiakiPacketStats *stats)
+{
+ chiaki_mutex_lock(&stats->mutex);
+ reset_stats(stats);
+ chiaki_mutex_unlock(&stats->mutex);
+}
+
+CHIAKI_EXPORT void chiaki_packet_stats_push_generation(ChiakiPacketStats *stats, uint64_t received, uint64_t lost)
+{
+ chiaki_mutex_lock(&stats->mutex);
+ stats->gen_received += received;
+ stats->gen_lost += lost;
+ chiaki_mutex_unlock(&stats->mutex);
+}
+
+CHIAKI_EXPORT void chiaki_packet_stats_push_seq(ChiakiPacketStats *stats, ChiakiSeqNum16 seq_num)
+{
+ stats->seq_received++;
+ if(chiaki_seq_num_16_gt(seq_num, stats->seq_max))
+ stats->seq_max = seq_num;
+}
+
+CHIAKI_EXPORT void chiaki_packet_stats_get(ChiakiPacketStats *stats, bool reset, uint64_t *received, uint64_t *lost)
+{
+ chiaki_mutex_lock(&stats->mutex);
+
+ // gen
+ *received = stats->gen_received;
+ *lost = stats->gen_lost;
+
+ //CHIAKI_LOGD(NULL, "gen received: %llu, lost: %llu",
+ // (unsigned long long)stats->gen_received,
+ // (unsigned long long)stats->gen_lost);
+
+ // seq
+ uint64_t seq_diff = stats->seq_max - stats->seq_min; // overflow on purpose if max < min
+ uint64_t seq_lost = stats->seq_received > seq_diff ? seq_diff : seq_diff - stats->seq_received;
+ *received += stats->seq_received;
+ *lost += seq_lost;
+
+ //CHIAKI_LOGD(NULL, "seq received: %llu, lost: %llu",
+ // (unsigned long long)stats->seq_received,
+ // (unsigned long long)seq_lost);
+
+ if(reset)
+ reset_stats(stats);
+ chiaki_mutex_unlock(&stats->mutex);
+}
diff --git a/lib/src/pb_utils.h b/lib/src/pb_utils.h
index 4183945..d4b30be 100644
--- a/lib/src/pb_utils.h
+++ b/lib/src/pb_utils.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_PB_UTILS_H
#define CHIAKI_PB_UTILS_H
@@ -21,7 +6,7 @@
#include
#include
-static bool chiaki_pb_encode_string(pb_ostream_t *stream, const pb_field_t *field, void *const *arg)
+static inline bool chiaki_pb_encode_string(pb_ostream_t *stream, const pb_field_t *field, void *const *arg)
{
char *str = *arg;
@@ -37,7 +22,7 @@ typedef struct chiaki_pb_buf_t
uint8_t *buf;
} ChiakiPBBuf;
-static bool chiaki_pb_encode_buf(pb_ostream_t *stream, const pb_field_t *field, void *const *arg)
+static inline bool chiaki_pb_encode_buf(pb_ostream_t *stream, const pb_field_t *field, void *const *arg)
{
ChiakiPBBuf *buf = *arg;
@@ -55,7 +40,7 @@ typedef struct chiaki_pb_decode_buf_t
uint8_t *buf;
} ChiakiPBDecodeBuf;
-static bool chiaki_pb_decode_buf(pb_istream_t *stream, const pb_field_t *field, void **arg)
+static inline bool chiaki_pb_decode_buf(pb_istream_t *stream, const pb_field_t *field, void **arg)
{
ChiakiPBDecodeBuf *buf = *arg;
if(stream->bytes_left > buf->max_size)
@@ -78,7 +63,7 @@ typedef struct chiaki_pb_decode_buf_alloc_t
uint8_t *buf;
} ChiakiPBDecodeBufAlloc;
-static bool chiaki_pb_decode_buf_alloc(pb_istream_t *stream, const pb_field_t *field, void **arg)
+static inline bool chiaki_pb_decode_buf_alloc(pb_istream_t *stream, const pb_field_t *field, void **arg)
{
ChiakiPBDecodeBufAlloc *buf = *arg;
buf->size = stream->bytes_left;
diff --git a/lib/src/pidecoder.c b/lib/src/pidecoder.c
new file mode 100644
index 0000000..ae99fbd
--- /dev/null
+++ b/lib/src/pidecoder.c
@@ -0,0 +1,242 @@
+
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_pi_decoder_init(ChiakiPiDecoder *decoder, ChiakiLog *log)
+{
+ memset(decoder, 0, sizeof(ChiakiPiDecoder));
+
+ bcm_host_init();
+
+ if(!(decoder->client = ilclient_init()))
+ {
+ CHIAKI_LOGE(decoder->log, "ilclient_init failed");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ if(OMX_Init() != OMX_ErrorNone)
+ {
+ CHIAKI_LOGE(decoder->log, "OMX_Init failed");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ if(ilclient_create_component(decoder->client, &decoder->video_decode, "video_decode",
+ ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0)
+ {
+ CHIAKI_LOGE(decoder->log, "ilclient_create_component failed for video_decode");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+ decoder->components[0] = decoder->video_decode;
+
+ if(ilclient_create_component(decoder->client, &decoder->video_render, "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0)
+ {
+ CHIAKI_LOGE(decoder->log, "ilclient_create_component failed for video_render");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+ decoder->components[1] = decoder->video_render;
+
+ set_tunnel(decoder->tunnel, decoder->video_decode, 131, decoder->video_render, 90);
+
+ ilclient_change_component_state(decoder->video_decode, OMX_StateIdle);
+
+ OMX_VIDEO_PARAM_PORTFORMATTYPE format;
+ memset(&format, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
+ format.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE);
+ format.nVersion.nVersion = OMX_VERSION;
+ format.nPortIndex = 130;
+ format.eCompressionFormat = OMX_VIDEO_CodingAVC;
+
+ OMX_PARAM_DATAUNITTYPE unit;
+ memset(&unit, 0, sizeof(OMX_PARAM_DATAUNITTYPE));
+ unit.nSize = sizeof(OMX_PARAM_DATAUNITTYPE);
+ unit.nVersion.nVersion = OMX_VERSION;
+ unit.nPortIndex = 130;
+ unit.eUnitType = OMX_DataUnitCodedPicture;
+ unit.eEncapsulationType = OMX_DataEncapsulationElementaryStream;
+
+ if(OMX_SetParameter(ILC_GET_HANDLE(decoder->video_decode), OMX_IndexParamVideoPortFormat, &format) != OMX_ErrorNone
+ || OMX_SetParameter(ILC_GET_HANDLE(decoder->video_decode), OMX_IndexParamBrcmDataUnit, &unit) != OMX_ErrorNone)
+ {
+ CHIAKI_LOGE(decoder->log, "OMX_SetParameter failed for video parameters");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ OMX_CONFIG_LATENCYTARGETTYPE latencyTarget;
+ memset(&latencyTarget, 0, sizeof(OMX_CONFIG_LATENCYTARGETTYPE));
+ latencyTarget.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+ latencyTarget.nVersion.nVersion = OMX_VERSION;
+ latencyTarget.nPortIndex = 90;
+ latencyTarget.bEnabled = OMX_TRUE;
+ latencyTarget.nFilter = 2;
+ latencyTarget.nTarget = 4000;
+ latencyTarget.nShift = 3;
+ latencyTarget.nSpeedFactor = -135;
+ latencyTarget.nInterFactor = 500;
+ latencyTarget.nAdjCap = 20;
+
+ if(OMX_SetParameter(ILC_GET_HANDLE(decoder->video_render), OMX_IndexConfigLatencyTarget, &latencyTarget) != OMX_ErrorNone)
+ {
+ CHIAKI_LOGE(decoder->log, "OMX_SetParameter failed for render parameters");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ OMX_CONFIG_ROTATIONTYPE rotationType;
+ memset(&rotationType, 0, sizeof(OMX_CONFIG_ROTATIONTYPE));
+ rotationType.nSize = sizeof(OMX_CONFIG_ROTATIONTYPE);
+ rotationType.nVersion.nVersion = OMX_VERSION;
+ rotationType.nPortIndex = 90;
+ //rotationType.nRotation = 90; // example
+
+ if(OMX_SetParameter(ILC_GET_HANDLE(decoder->video_render), OMX_IndexConfigCommonRotate, &rotationType) != OMX_ErrorNone)
+ {
+ CHIAKI_LOGE(decoder->log, "OMX_SetParameter failed for rotation");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ if(ilclient_enable_port_buffers(decoder->video_decode, 130, NULL, NULL, NULL) != 0)
+ {
+ CHIAKI_LOGE(decoder->log, "ilclient_enable_port_buffers failed");
+ chiaki_pi_decoder_fini(decoder);
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ decoder->port_settings_changed = false;
+ decoder->first_packet = true;
+
+ ilclient_change_component_state(decoder->video_decode, OMX_StateExecuting);
+
+ CHIAKI_LOGI(decoder->log, "Raspberry Pi Decoder initialized");
+ return CHIAKI_ERR_SUCCESS;
+}
+
+CHIAKI_EXPORT void chiaki_pi_decoder_fini(ChiakiPiDecoder *decoder)
+{
+ if(decoder->video_decode)
+ {
+ OMX_BUFFERHEADERTYPE *buf;
+ if((buf = ilclient_get_input_buffer(decoder->video_decode, 130, 1)))
+ {
+ buf->nFilledLen = 0;
+ buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS;
+ OMX_EmptyThisBuffer(ILC_GET_HANDLE(decoder->video_decode), buf);
+ }
+
+ // need to flush the renderer to allow video_decode to disable its input port
+ ilclient_flush_tunnels(decoder->tunnel, 0);
+
+ ilclient_disable_port_buffers(decoder->video_decode, 130, NULL, NULL, NULL);
+
+ ilclient_disable_tunnel(decoder->tunnel);
+ ilclient_teardown_tunnels(decoder->tunnel);
+
+ ilclient_state_transition(decoder->components, OMX_StateIdle);
+ ilclient_state_transition(decoder->components, OMX_StateLoaded);
+
+ ilclient_cleanup_components(decoder->components);
+ }
+
+ OMX_Deinit();
+ if(decoder->client)
+ ilclient_destroy(decoder->client);
+}
+
+static bool push_buffer(ChiakiPiDecoder *decoder, uint8_t *buf, size_t buf_size)
+{
+ while(buf_size)
+ {
+ OMX_BUFFERHEADERTYPE *omx_buf = ilclient_get_input_buffer(decoder->video_decode, 130, 1);
+ if(!omx_buf)
+ {
+ CHIAKI_LOGE(decoder->log, "ilclient_get_input_buffer failed");
+ return false;
+ }
+
+ size_t push_size = buf_size;
+ if(push_size > omx_buf->nAllocLen)
+ {
+ CHIAKI_LOGW(decoder->log, "OMX Buffer too small, fragmenting to multiple buffers.");
+ push_size = omx_buf->nAllocLen;
+ }
+ memcpy(omx_buf->pBuffer, buf, push_size);
+ buf_size -= push_size;
+ buf += push_size;
+ omx_buf->nFilledLen = push_size;
+ omx_buf->nOffset = 0;
+ omx_buf->nFlags = 0;
+ if(!buf_size)
+ omx_buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME;
+ if(decoder->first_packet)
+ {
+ omx_buf->nFlags |= OMX_BUFFERFLAG_STARTTIME;
+ decoder->first_packet = false;
+ }
+
+ if(!decoder->port_settings_changed
+ && ((omx_buf->nFilledLen > 0 && ilclient_remove_event(decoder->video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1) == 0)
+ || (omx_buf->nFilledLen == 0 && ilclient_wait_for_event(decoder->video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1, ILCLIENT_EVENT_ERROR | ILCLIENT_PARAMETER_CHANGED, 10000) == 0)))
+ {
+ decoder->port_settings_changed = true;
+
+ if(ilclient_setup_tunnel(decoder->tunnel, 0, 0) != 0)
+ {
+ CHIAKI_LOGE(decoder->log, "ilclient_setup_tunnel failed");
+ return false;
+ }
+
+ ilclient_change_component_state(decoder->video_render, OMX_StateExecuting);
+ }
+
+ if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(decoder->video_decode), omx_buf) != OMX_ErrorNone)
+ {
+ CHIAKI_LOGE(decoder->log, "OMX_EmptyThisBuffer failed");
+ return false;
+ }
+ }
+ return true;
+}
+
+#define OMX_INIT_STRUCTURE(a) \
+ memset(&(a), 0, sizeof(a)); \
+ (a).nSize = sizeof(a); \
+ (a).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
+ (a).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
+ (a).nVersion.s.nRevision = OMX_VERSION_REVISION; \
+ (a).nVersion.s.nStep = OMX_VERSION_STEP
+
+CHIAKI_EXPORT void chiaki_pi_decoder_set_params(ChiakiPiDecoder *decoder, int x, int y, int w, int h, bool visible)
+{
+ OMX_CONFIG_DISPLAYREGIONTYPE configDisplay;
+ OMX_INIT_STRUCTURE(configDisplay);
+ configDisplay.nPortIndex = 90;
+ configDisplay.set = (OMX_DISPLAYSETTYPE)(OMX_DISPLAY_SET_NOASPECT | OMX_DISPLAY_SET_MODE | OMX_DISPLAY_SET_FULLSCREEN | OMX_DISPLAY_SET_PIXEL | OMX_DISPLAY_SET_DEST_RECT | OMX_DISPLAY_SET_ALPHA);
+ configDisplay.mode = OMX_DISPLAY_MODE_LETTERBOX;
+ configDisplay.fullscreen = OMX_FALSE;
+ configDisplay.noaspect = OMX_FALSE;
+ configDisplay.dest_rect.x_offset = x;
+ configDisplay.dest_rect.y_offset = y;
+ configDisplay.dest_rect.width = w;
+ configDisplay.dest_rect.height = h;
+ configDisplay.alpha = visible ? 255 : 0;
+
+ if(OMX_SetParameter(ILC_GET_HANDLE(decoder->video_render), OMX_IndexConfigDisplayRegion, &configDisplay) != OMX_ErrorNone)
+ CHIAKI_LOGE(decoder->log, "OMX_SetParameter failed for display params");
+}
+
+CHIAKI_EXPORT bool chiaki_pi_decoder_video_sample_cb(uint8_t *buf, size_t buf_size, void *user)
+{
+ return push_buffer(user, buf, buf_size);
+}
diff --git a/lib/src/random.c b/lib/src/random.c
index beaf373..158ce4d 100644
--- a/lib/src/random.c
+++ b/lib/src/random.c
@@ -1,33 +1,55 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+//#include
+#include
+#include
+#else
#include
+#endif
CHIAKI_EXPORT ChiakiErrorCode chiaki_random_bytes_crypt(uint8_t *buf, size_t buf_size)
{
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+ // mbedtls_havege_state hs;
+ // mbedtls_havege_init(&hs);
+ // int r = mbedtls_havege_random( &hs, buf, sizeof( buf ) );
+ // if(r != 0 )
+ // return CHIAKI_ERR_UNKNOWN;
+ // return CHIAKI_ERR_SUCCESS;
+
+ // https://github.com/ARMmbed/mbedtls/blob/development/programs/random/gen_random_ctr_drbg.c
+ mbedtls_ctr_drbg_context ctr_drbg;
+ mbedtls_entropy_context entropy;
+
+ mbedtls_ctr_drbg_init(&ctr_drbg);
+ mbedtls_entropy_init(&entropy);
+
+ mbedtls_ctr_drbg_set_prediction_resistance(&ctr_drbg, MBEDTLS_CTR_DRBG_PR_OFF);
+ if(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)"RANDOM_GEN", 10) != 0)
+ {
+ return CHIAKI_ERR_UNKNOWN;
+ }
+ if(mbedtls_ctr_drbg_random(&ctr_drbg, buf, buf_size) != 0)
+ {
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ mbedtls_ctr_drbg_free(&ctr_drbg);
+ mbedtls_entropy_free(&entropy);
+ return CHIAKI_ERR_SUCCESS;
+
+#else
int r = RAND_bytes(buf, (int)buf_size);
if(!r)
return CHIAKI_ERR_UNKNOWN;
return CHIAKI_ERR_SUCCESS;
+#endif
}
CHIAKI_EXPORT uint32_t chiaki_random_32()
{
return rand() % UINT32_MAX;
-}
\ No newline at end of file
+}
diff --git a/lib/src/regist.c b/lib/src/regist.c
index 947b713..af17942 100644
--- a/lib/src/regist.c
+++ b/lib/src/regist.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include "utils.h"
@@ -57,17 +42,21 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_regist_start(ChiakiRegist *regist, ChiakiLo
if(!regist->info.host)
return CHIAKI_ERR_MEMORY;
+ ChiakiErrorCode err = CHIAKI_ERR_UNKNOWN;
if(regist->info.psn_online_id)
{
regist->info.psn_online_id = strdup(regist->info.psn_online_id);
if(!regist->info.psn_online_id)
+ {
+ err = CHIAKI_ERR_MEMORY;
goto error_host;
+ }
}
regist->cb = cb;
regist->cb_user = cb_user;
- ChiakiErrorCode err = chiaki_stop_pipe_init(®ist->stop_pipe);
+ err = chiaki_stop_pipe_init(®ist->stop_pipe);
if(err != CHIAKI_ERR_SUCCESS)
goto error_psn_id;
@@ -106,34 +95,57 @@ static void regist_event_simple(ChiakiRegist *regist, ChiakiRegistEventType type
regist->cb(&event, regist->cb_user);
}
-static const char * const request_head_fmt =
- "POST /sce/rp/regist HTTP/1.1\r\n"
+static const char *const request_head_fmt =
+ "POST %s HTTP/1.1\r\n HTTP/1.1\r\n"
"HOST: 10.0.2.15\r\n" // random lol
"User-Agent: remoteplay Windows\r\n"
"Connection: close\r\n"
"Content-Length: %llu\r\n";
-static const char * const request_rp_version_fmt = "RP-Version: %s\r\n";
+static const char *request_path_ps5 = "/sie/ps5/rp/sess/rgst";
+static const char *request_path_ps4 = "/sie/ps4/rp/sess/rgst";
+static const char *request_path_ps4_pre10 = "/sce/rp/regist";
-static const char * const request_tail = "\r\n";
+static const char *request_path(ChiakiTarget target)
+{
+ switch(target)
+ {
+ case CHIAKI_TARGET_PS5_UNKNOWN:
+ case CHIAKI_TARGET_PS5_1:
+ return request_path_ps5;
+ case CHIAKI_TARGET_PS4_8:
+ case CHIAKI_TARGET_PS4_9:
+ return request_path_ps4_pre10;
+ default:
+ return request_path_ps4;
+ }
+}
-static const char * const request_inner_account_id_fmt =
- "Client-Type: Windows\r\n"
+static const char *const request_rp_version_fmt = "RP-Version: %s\r\n";
+
+static const char *const request_tail = "\r\n";
+
+const char *client_type = "dabfa2ec873de5839bee8d3f4c0239c4282c07c25c6077a2931afcf0adc0d34f";
+const char *client_type_ps4_pre10 = "Windows";
+
+static const char *const request_inner_account_id_fmt =
+ "Client-Type: %s\r\n"
"Np-AccountId: %s\r\n";
-static const char * const request_inner_online_id_fmt =
+static const char *const request_inner_online_id_fmt =
"Client-Type: Windows\r\n"
"Np-Online-Id: %s\r\n";
-static int request_header_format(char *buf, size_t buf_size, size_t payload_size, ChiakiRpVersion rp_version)
+static int request_header_format(char *buf, size_t buf_size, size_t payload_size, ChiakiTarget target)
{
- int cur = snprintf(buf, buf_size, request_head_fmt, (unsigned long long)payload_size);
+ int cur = snprintf(buf, buf_size, request_head_fmt, request_path(target),
+ (unsigned long long)payload_size);
if(cur < 0 || cur >= payload_size)
return -1;
- if(rp_version >= CHIAKI_RP_VERSION_9_0)
+ if(target >= CHIAKI_TARGET_PS4_9)
{
- const char *rp_version_str = chiaki_rp_version_string(rp_version);
+ const char *rp_version_str = chiaki_rp_version_string(target);
size_t s = buf_size - cur;
int r = snprintf(buf + cur, s, request_rp_version_fmt, rp_version_str);
if(r < 0 || r >= s)
@@ -148,15 +160,35 @@ static int request_header_format(char *buf, size_t buf_size, size_t payload_size
return cur;
}
-
-CHIAKI_EXPORT ChiakiErrorCode chiaki_regist_request_payload_format(uint8_t *buf, size_t *buf_size, ChiakiRPCrypt *crypt, const char *psn_online_id, const uint8_t *psn_account_id)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_regist_request_payload_format(ChiakiTarget target, const uint8_t *ambassador, uint8_t *buf, size_t *buf_size, ChiakiRPCrypt *crypt, const char *psn_online_id, const uint8_t *psn_account_id, uint32_t pin)
{
size_t buf_size_val = *buf_size;
static const size_t inner_header_off = 0x1e0;
if(buf_size_val < inner_header_off)
return CHIAKI_ERR_BUF_TOO_SMALL;
- memset(buf, 'A', inner_header_off);
- chiaki_rpcrypt_aeropause(buf + 0x11c, crypt->ambassador);
+ memset(buf, 'A', inner_header_off); // can be random
+
+ if(target < CHIAKI_TARGET_PS4_10)
+ {
+ chiaki_rpcrypt_init_regist_ps4_pre10(crypt, ambassador, pin);
+ chiaki_rpcrypt_aeropause_ps4_pre10(buf + 0x11c, crypt->ambassador);
+ }
+ else
+ {
+ size_t key_0_off = buf[0x18D] & 0x1F;
+ size_t key_1_off = buf[0] >> 3;
+ ChiakiErrorCode err = chiaki_rpcrypt_init_regist(crypt, target, ambassador, key_0_off, pin);
+ if(err != CHIAKI_ERR_SUCCESS)
+ return err;
+ uint8_t aeropause[0x10];
+ err = chiaki_rpcrypt_aeropause(target, key_1_off, aeropause, crypt->ambassador);
+ if(err != CHIAKI_ERR_SUCCESS)
+ return err;
+ memcpy(buf + 0xc7, aeropause + 8, 8);
+ memcpy(buf + 0x191, aeropause, 8);
+ psn_online_id = NULL; // don't need this
+ }
+
int inner_header_size;
if(psn_online_id)
inner_header_size = snprintf((char *)buf + inner_header_off, buf_size_val - inner_header_off, request_inner_online_id_fmt, psn_online_id);
@@ -166,7 +198,10 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_regist_request_payload_format(uint8_t *buf,
ChiakiErrorCode err = chiaki_base64_encode(psn_account_id, CHIAKI_PSN_ACCOUNT_ID_SIZE, account_id_b64, sizeof(account_id_b64));
if(err != CHIAKI_ERR_SUCCESS)
return err;
- inner_header_size = snprintf((char *)buf + inner_header_off, buf_size_val - inner_header_off, request_inner_account_id_fmt, account_id_b64);
+ inner_header_size = snprintf((char *)buf + inner_header_off, buf_size_val - inner_header_off,
+ request_inner_account_id_fmt,
+ target < CHIAKI_TARGET_PS4_10 ? client_type_ps4_pre10 : client_type,
+ account_id_b64);
}
else
return CHIAKI_ERR_INVALID_DATA;
@@ -184,29 +219,26 @@ static void *regist_thread_func(void *user)
bool canceled = false;
bool success = false;
+ ChiakiRPCrypt crypt;
uint8_t ambassador[CHIAKI_RPCRYPT_KEY_SIZE];
ChiakiErrorCode err = chiaki_random_bytes_crypt(ambassador, sizeof(ambassador));
if(err != CHIAKI_ERR_SUCCESS)
{
- CHIAKI_LOGE(regist->log, "Regist failed to generate random nonce");
+ CHIAKI_LOGE(regist->log, "Regist failed to generate random ambassador");
goto fail;
}
- ChiakiRPCrypt crypt;
- chiaki_rpcrypt_init_regist(&crypt, ambassador, regist->info.pin);
-
uint8_t payload[0x400];
size_t payload_size = sizeof(payload);
- err = chiaki_regist_request_payload_format(payload, &payload_size, &crypt, regist->info.psn_online_id, regist->info.psn_account_id);
+ err = chiaki_regist_request_payload_format(regist->info.target, ambassador, payload, &payload_size, &crypt, regist->info.psn_online_id, regist->info.psn_account_id, regist->info.pin);
if(err != CHIAKI_ERR_SUCCESS)
{
CHIAKI_LOGE(regist->log, "Regist failed to format payload");
goto fail;
}
- ChiakiRpVersion rp_version = regist->info.psn_online_id ? CHIAKI_RP_VERSION_8_0 : CHIAKI_RP_VERSION_9_0;
char request_header[0x100];
- int request_header_size = request_header_format(request_header, sizeof(request_header), payload_size, rp_version);
+ int request_header_size = request_header_format(request_header, sizeof(request_header), payload_size, regist->info.target);
if(request_header_size < 0 || request_header_size >= sizeof(request_header))
{
@@ -329,12 +361,16 @@ static ChiakiErrorCode regist_search(ChiakiRegist *regist, struct addrinfo *addr
ChiakiErrorCode err = CHIAKI_ERR_SUCCESS;
+ const char *src = chiaki_target_is_ps5(regist->info.target) ? "SRC3" : "SRC2";
+ const char *res = chiaki_target_is_ps5(regist->info.target) ? "RES3" : "RES2";
+ size_t res_size = strlen(res);
+
CHIAKI_LOGI(regist->log, "Regist sending search packet");
int r;
if(regist->info.broadcast)
- r = sendto_broadcast(regist->log, sock, "SRC2", 4, 0, &send_addr, send_addr_len);
+ r = sendto_broadcast(regist->log, sock, src, strlen(src) + 1, 0, &send_addr, send_addr_len);
else
- r = send(sock, "SRC2", 4, 0);
+ r = send(sock, src, strlen(src) + 1, 0);
if(r < 0)
{
CHIAKI_LOGE(regist->log, "Regist failed to send search: %s", strerror(errno));
@@ -369,10 +405,10 @@ static ChiakiErrorCode regist_search(ChiakiRegist *regist, struct addrinfo *addr
goto done;
}
- CHIAKI_LOGV(regist->log, "Regist received packet:");
+ CHIAKI_LOGV(regist->log, "Regist received packet: %d >= %d", n, res_size);
chiaki_log_hexdump(regist->log, CHIAKI_LOG_VERBOSE, buf, n);
- if(n >= 4 && memcmp(buf, "RES2", 4) == 0)
+ if(n >= res_size && !memcmp(buf, res, res_size))
{
char addr[64];
const char *addr_str = sockaddr_str(recv_addr, addr, sizeof(addr));
@@ -404,7 +440,7 @@ static chiaki_socket_t regist_search_connect(ChiakiRegist *regist, struct addrin
set_port(send_addr, htons(REGIST_PORT));
- sock = socket(ai->ai_family, SOCK_DGRAM, 0);
+ sock = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP);
if(CHIAKI_SOCKET_IS_INVALID(sock))
{
CHIAKI_LOGE(regist->log, "Regist failed to create socket for search");
@@ -596,20 +632,22 @@ static ChiakiErrorCode regist_parse_response_payload(ChiakiRegist *regist, Chiak
}
memset(host, 0, sizeof(*host));
+ host->target = regist->info.target;
bool mac_found = false;
bool regist_key_found = false;
bool key_found = false;
+ bool ps5 = chiaki_target_is_ps5(regist->info.target);
for(ChiakiHttpHeader *header=headers; header; header=header->next)
{
#define COPY_STRING(name, key_str) \
- if(strcmp(header->key, key_str) == 0) \
+ if(strcmp(header->key, (key_str)) == 0) \
{ \
size_t len = strlen(header->value); \
if(len >= sizeof(host->name)) \
{ \
- CHIAKI_LOGE(regist->log, "Regist value for " key_str " in response is too long"); \
+ CHIAKI_LOGE(regist->log, "Regist value for %s in response is too long", (key_str)); \
continue; \
} \
memcpy(host->name, header->value, len); \
@@ -620,10 +658,10 @@ static ChiakiErrorCode regist_parse_response_payload(ChiakiRegist *regist, Chiak
COPY_STRING(ap_bssid, "AP-Bssid")
COPY_STRING(ap_key, "AP-Key")
COPY_STRING(ap_name, "AP-Name")
- COPY_STRING(ps4_nickname, "PS4-Nickname")
+ COPY_STRING(server_nickname, ps5 ? "PS5-Nickname" : "PS4-Nickname")
#undef COPY_STRING
- if(strcmp(header->key, "PS4-RegistKey") == 0)
+ if(strcmp(header->key, ps5 ? "PS5-RegistKey" : "PS4-RegistKey") == 0)
{
memset(host->rp_regist_key, 0, sizeof(host->rp_regist_key));
size_t buf_size = sizeof(host->rp_regist_key);
@@ -656,14 +694,14 @@ static ChiakiErrorCode regist_parse_response_payload(ChiakiRegist *regist, Chiak
key_found = true;
}
}
- else if(strcmp(header->key, "PS4-Mac") == 0)
+ else if(strcmp(header->key, ps5 ? "PS5-Mac" : "PS4-Mac") == 0)
{
- size_t buf_size = sizeof(host->ps4_mac);
- err = parse_hex((uint8_t *)host->ps4_mac, &buf_size, header->value, strlen(header->value));
- if(err != CHIAKI_ERR_SUCCESS || buf_size != sizeof(host->ps4_mac))
+ size_t buf_size = sizeof(host->server_mac);
+ err = parse_hex((uint8_t *)host->server_mac, &buf_size, header->value, strlen(header->value));
+ if(err != CHIAKI_ERR_SUCCESS || buf_size != sizeof(host->server_mac))
{
CHIAKI_LOGE(regist->log, "Regist received invalid MAC Address in response");
- memset(host->ps4_mac, 0, sizeof(host->ps4_mac));
+ memset(host->server_mac, 0, sizeof(host->server_mac));
}
else
{
diff --git a/lib/src/reorderqueue.c b/lib/src/reorderqueue.c
index 3f800ac..bd007fe 100644
--- a/lib/src/reorderqueue.c
+++ b/lib/src/reorderqueue.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
diff --git a/lib/src/rpcrypt.c b/lib/src/rpcrypt.c
index 708947f..15b64d0 100644
--- a/lib/src/rpcrypt.c
+++ b/lib/src/rpcrypt.c
@@ -1,31 +1,22 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
+#include "mbedtls/aes.h"
+#include "mbedtls/md.h"
+#else
#include
#include
+#endif
#include
#include
+#include
static const uint8_t echo_b[] = { 0xe1, 0xec, 0x9c, 0x3a, 0xdd, 0xbd, 0x08, 0x85, 0xfc, 0x0e, 0x1d, 0x78, 0x90, 0x32, 0xc0, 0x04 };
-CHIAKI_EXPORT void chiaki_rpcrypt_bright_ambassador(uint8_t *bright, uint8_t *ambassador, const uint8_t *nonce, const uint8_t *morning)
+static void bright_ambassador_ps4_pre10(uint8_t *bright, uint8_t *ambassador, const uint8_t *nonce, const uint8_t *morning)
{
static const uint8_t echo_a[] = { 0x01, 0x49, 0x87, 0x9b, 0x65, 0x39, 0x8b, 0x39, 0x4b, 0x3a, 0x8d, 0x48, 0xc3, 0x0a, 0xef, 0x51 };
@@ -49,7 +40,1524 @@ CHIAKI_EXPORT void chiaki_rpcrypt_bright_ambassador(uint8_t *bright, uint8_t *am
}
}
-CHIAKI_EXPORT void chiaki_rpcrypt_aeropause(uint8_t *aeropause, const uint8_t *ambassador)
+static ChiakiErrorCode bright_ambassador(ChiakiTarget target, uint8_t *bright, uint8_t *ambassador, const uint8_t *nonce, const uint8_t *morning)
+{
+ static const uint8_t keys_a_ps4[0x70 * 0x20] = {
+ 0xdf, 0x40, 0x82, 0xa6, 0x2e, 0xd8, 0x6b, 0x2b, 0xf5, 0x6c,
+ 0x5f, 0xf5, 0xfb, 0x21, 0x69, 0xb4, 0x00, 0x27, 0x5e, 0x87,
+ 0xfa, 0x1a, 0xa7, 0x95, 0x16, 0xa1, 0xb7, 0xb7, 0xca, 0xf2,
+ 0x3d, 0x7a, 0x04, 0x97, 0xef, 0x49, 0xeb, 0x51, 0xbc, 0xe9,
+ 0x2a, 0x47, 0x84, 0xba, 0xfb, 0x08, 0x78, 0x4a, 0xa7, 0x82,
+ 0x17, 0xad, 0x4b, 0x26, 0x34, 0x97, 0xb2, 0xde, 0x41, 0x7d,
+ 0xc6, 0x08, 0xc4, 0xe9, 0xe7, 0x73, 0xa1, 0x22, 0xa2, 0xf3,
+ 0x2a, 0x6f, 0x8c, 0xc0, 0x45, 0xf6, 0x84, 0xb4, 0xd7, 0x54,
+ 0x07, 0xd5, 0x76, 0x33, 0xd3, 0x8b, 0x61, 0xe7, 0xea, 0xd6,
+ 0x98, 0x32, 0xf5, 0xeb, 0x9e, 0x1b, 0xbd, 0xe2, 0x01, 0x31,
+ 0x45, 0x01, 0x0d, 0x51, 0x1f, 0x77, 0xaf, 0x0c, 0x34, 0xb7,
+ 0x23, 0x40, 0x0d, 0xc0, 0xac, 0x7e, 0x04, 0xb5, 0xf0, 0x75,
+ 0xde, 0x5e, 0xba, 0xfa, 0xa6, 0x25, 0x56, 0xde, 0x53, 0x83,
+ 0x94, 0x4e, 0xa4, 0xd9, 0x4e, 0xf4, 0x73, 0xab, 0xd6, 0x96,
+ 0xb6, 0xfc, 0x09, 0x46, 0xe2, 0x1a, 0x9d, 0x4f, 0xf1, 0x89,
+ 0x90, 0xa0, 0x34, 0xf4, 0x55, 0x60, 0x29, 0xf9, 0x39, 0xdb,
+ 0x20, 0xaa, 0xdf, 0x8b, 0xff, 0x6c, 0xc0, 0xa9, 0x96, 0xad,
+ 0x71, 0x67, 0xae, 0xb6, 0x1a, 0x6e, 0xd4, 0x92, 0x7a, 0xc7,
+ 0xac, 0x31, 0xc8, 0x21, 0x24, 0x3b, 0xb3, 0x6a, 0x1d, 0x4d,
+ 0xc0, 0x82, 0x5f, 0x53, 0x9f, 0xf9, 0xeb, 0x4a, 0x41, 0x47,
+ 0x27, 0x5a, 0xbc, 0x3b, 0xc8, 0x50, 0x20, 0xeb, 0x11, 0x02,
+ 0xf8, 0xc5, 0x7e, 0xa4, 0xd3, 0x85, 0x52, 0x90, 0x61, 0x4e,
+ 0x88, 0xff, 0x81, 0xcf, 0x75, 0x58, 0x21, 0xfe, 0x58, 0x8f,
+ 0xd0, 0x75, 0xf6, 0x20, 0xaa, 0x54, 0x3a, 0x45, 0x54, 0xf3,
+ 0xe4, 0x44, 0xda, 0x98, 0x7f, 0x09, 0x9e, 0xa6, 0x72, 0x63,
+ 0xbc, 0x9f, 0x46, 0x77, 0x7e, 0x24, 0xf8, 0xda, 0xc4, 0x94,
+ 0x04, 0xa9, 0x23, 0xa7, 0xea, 0x67, 0xe3, 0x85, 0x41, 0xa4,
+ 0x4b, 0x8e, 0xc1, 0x92, 0xde, 0x96, 0xc8, 0x09, 0xb7, 0xd4,
+ 0x41, 0x60, 0x89, 0xf0, 0xdb, 0x09, 0x56, 0x01, 0xc1, 0x6c,
+ 0x33, 0xb1, 0x37, 0xbd, 0x79, 0x6f, 0xab, 0x1c, 0x61, 0xe9,
+ 0x18, 0xfc, 0xb3, 0x6c, 0xbc, 0xfe, 0xea, 0xb1, 0x9e, 0xce,
+ 0xba, 0x83, 0xda, 0xe5, 0x81, 0xd7, 0x07, 0xb7, 0x8a, 0xf0,
+ 0x89, 0x39, 0xdc, 0x30, 0x6b, 0x0d, 0xa7, 0x2b, 0xc9, 0x51,
+ 0xdc, 0x4f, 0x99, 0x98, 0x3c, 0xcf, 0x62, 0x1c, 0x56, 0xf1,
+ 0x86, 0x88, 0xdb, 0xce, 0x10, 0xbf, 0x3d, 0xde, 0xe6, 0xf5,
+ 0x02, 0x51, 0xf7, 0xa5, 0xe3, 0x0a, 0xdb, 0xeb, 0xae, 0xb0,
+ 0x76, 0x89, 0x62, 0x4b, 0xa7, 0xbd, 0xd6, 0xfe, 0x8d, 0x01,
+ 0x28, 0x26, 0xf3, 0x76, 0x65, 0xba, 0x0f, 0x44, 0x77, 0x65,
+ 0x79, 0x47, 0x06, 0x43, 0xae, 0xa8, 0xd1, 0x5e, 0x20, 0x0e,
+ 0x72, 0x2a, 0x15, 0xd9, 0x42, 0x96, 0xe7, 0xdb, 0x40, 0xae,
+ 0x4e, 0x2b, 0x1c, 0x6d, 0x71, 0xa2, 0x8e, 0xa6, 0xe1, 0x4d,
+ 0xe8, 0x84, 0xa3, 0xa5, 0x59, 0x41, 0x0c, 0x9e, 0xe9, 0x3e,
+ 0x5a, 0xc8, 0x02, 0x3f, 0xbb, 0xf3, 0xe1, 0x0a, 0xf7, 0x3d,
+ 0xd7, 0xaa, 0xe5, 0x64, 0xc6, 0x4e, 0xc3, 0xc9, 0xbb, 0x32,
+ 0x30, 0xa9, 0x18, 0xec, 0xb8, 0xae, 0x61, 0xc9, 0x1a, 0x62,
+ 0xeb, 0x47, 0x92, 0x0c, 0xa7, 0xe0, 0x6c, 0x33, 0xf5, 0x84,
+ 0xec, 0x69, 0x31, 0xdb, 0xad, 0xdf, 0x3d, 0xbb, 0x4c, 0x4b,
+ 0xa1, 0x8c, 0xf0, 0x46, 0x52, 0x65, 0x72, 0xaf, 0x6a, 0xc8,
+ 0xce, 0xb3, 0x27, 0xd3, 0x32, 0x78, 0xb1, 0x39, 0xd6, 0xd1,
+ 0x5e, 0x09, 0x06, 0xff, 0xdc, 0x8f, 0xca, 0x16, 0x9b, 0x5e,
+ 0xa2, 0x18, 0x64, 0xee, 0x8c, 0x0b, 0x9b, 0xd7, 0x41, 0xf8,
+ 0x35, 0x98, 0xd3, 0x5c, 0x78, 0xe2, 0xbb, 0xa5, 0xf7, 0x68,
+ 0x02, 0x8a, 0xd2, 0x14, 0x85, 0x50, 0x8b, 0x98, 0x2a, 0x09,
+ 0x15, 0xc0, 0x9e, 0x9c, 0x47, 0x1f, 0x92, 0xc2, 0xa5, 0x2a,
+ 0x3b, 0xaa, 0x7b, 0xde, 0xb8, 0xe7, 0xee, 0xa7, 0xe0, 0x4a,
+ 0xb4, 0x81, 0x28, 0x7f, 0x15, 0x38, 0xe8, 0x78, 0xff, 0xc1,
+ 0xed, 0x98, 0xd8, 0x4f, 0x23, 0x3f, 0x77, 0x29, 0xac, 0xa5,
+ 0xbc, 0xd0, 0x8f, 0x2b, 0x89, 0xb9, 0x99, 0xbe, 0xc1, 0xf1,
+ 0x3e, 0x4e, 0xbd, 0x30, 0xd7, 0x6c, 0x1c, 0x09, 0x4e, 0x7c,
+ 0x13, 0x28, 0xf5, 0xc3, 0xff, 0xc1, 0xcf, 0x8a, 0x2a, 0x41,
+ 0xd0, 0x73, 0x24, 0x23, 0x31, 0x13, 0x77, 0xa4, 0x33, 0xc0,
+ 0x7e, 0x7b, 0xc0, 0x81, 0xb5, 0xf5, 0x25, 0x80, 0x45, 0xc5,
+ 0x94, 0x65, 0xa4, 0x8a, 0x94, 0xc1, 0xb6, 0xf4, 0x69, 0x61,
+ 0x11, 0xd5, 0x43, 0xea, 0x4b, 0x38, 0x4a, 0x56, 0x14, 0xdd,
+ 0x3b, 0xa6, 0x99, 0xdf, 0xe5, 0x87, 0x2a, 0xf9, 0x12, 0xbf,
+ 0x45, 0xb1, 0xd9, 0x74, 0x67, 0x8b, 0x2b, 0x57, 0xc3, 0xd0,
+ 0xb1, 0x9a, 0x4a, 0xcc, 0x4f, 0x63, 0x79, 0x5a, 0x4f, 0x1d,
+ 0xa6, 0xff, 0x65, 0xee, 0xbe, 0xd9, 0x16, 0xea, 0xa9, 0x66,
+ 0x07, 0x97, 0x71, 0x43, 0xdd, 0x64, 0xe3, 0x00, 0x03, 0xbc,
+ 0xba, 0x1b, 0xf3, 0xa6, 0xa6, 0xee, 0xef, 0xf8, 0xa7, 0xf2,
+ 0x15, 0xf9, 0xcf, 0x8c, 0x62, 0x2a, 0x1e, 0x9b, 0xbe, 0xfb,
+ 0x5a, 0xb7, 0xed, 0xd5, 0xd2, 0x71, 0x2a, 0xc9, 0xeb, 0x65,
+ 0x68, 0x77, 0x34, 0xa4, 0xbb, 0x44, 0xab, 0x01, 0xe5, 0x79,
+ 0x19, 0x37, 0xb2, 0x07, 0x01, 0xc6, 0xca, 0xb3, 0x07, 0xb8,
+ 0x55, 0xac, 0x03, 0xae, 0xf6, 0x3f, 0x29, 0x12, 0xa1, 0xba,
+ 0x1d, 0x94, 0x3a, 0xa3, 0xb5, 0x6c, 0x6e, 0xf4, 0x31, 0x3f,
+ 0x38, 0xa7, 0xe0, 0x34, 0x31, 0xf2, 0x2a, 0x7a, 0xe6, 0xcf,
+ 0x3f, 0xc4, 0xe9, 0xb9, 0x0f, 0x05, 0x9f, 0xec, 0x9a, 0xdc,
+ 0xb3, 0x59, 0xc4, 0x61, 0xaa, 0x10, 0xb6, 0xce, 0x73, 0xe3,
+ 0xe3, 0x8c, 0xf8, 0x5d, 0x0e, 0x95, 0x3e, 0x1a, 0x4f, 0xbe,
+ 0x77, 0x87, 0xce, 0x8a, 0x17, 0xf5, 0x20, 0xa7, 0x01, 0xf0,
+ 0xd7, 0xe6, 0x34, 0x63, 0x04, 0x76, 0xbe, 0x74, 0xb3, 0x44,
+ 0x7d, 0xdf, 0x11, 0x32, 0xb9, 0x13, 0x32, 0x15, 0x1a, 0xed,
+ 0xcf, 0x1f, 0x85, 0x91, 0xdb, 0xa3, 0xc4, 0xf2, 0x0f, 0x2c,
+ 0x8f, 0x77, 0x0a, 0xc4, 0x09, 0x7d, 0x19, 0x12, 0x7c, 0x46,
+ 0xf7, 0x9d, 0x3a, 0x94, 0x86, 0x4b, 0x22, 0x67, 0x6f, 0x90,
+ 0x57, 0x99, 0x70, 0x28, 0x3d, 0x9c, 0x53, 0xd2, 0xd3, 0x0b,
+ 0xc1, 0x29, 0xb7, 0x39, 0x8c, 0xb7, 0x62, 0xc8, 0xbb, 0x64,
+ 0x4d, 0xcf, 0xe0, 0x5f, 0x64, 0xfc, 0x47, 0xb2, 0xef, 0x0a,
+ 0x68, 0x8d, 0x9a, 0xcd, 0x9f, 0x49, 0x77, 0x6b, 0x50, 0x47,
+ 0x7c, 0xfc, 0xa4, 0xd4, 0x2c, 0x4d, 0x53, 0xc1, 0x26, 0x26,
+ 0x92, 0x38, 0x0d, 0xba, 0x1c, 0x71, 0x81, 0x17, 0x4a, 0x59,
+ 0x0f, 0x80, 0xfd, 0xad, 0x69, 0x5c, 0x58, 0xa7, 0xff, 0xef,
+ 0x1a, 0xca, 0xd6, 0xfb, 0x7e, 0x09, 0xdc, 0x32, 0x17, 0x34,
+ 0x31, 0x9c, 0xfa, 0x78, 0xf9, 0x88, 0x1a, 0x50, 0x6b, 0x09,
+ 0x24, 0xfe, 0x75, 0xcd, 0x22, 0xbd, 0xf0, 0x12, 0x10, 0x9d,
+ 0xc5, 0x7b, 0xbe, 0x9f, 0xa4, 0xc8, 0x7a, 0x7c, 0xb1, 0xa7,
+ 0x46, 0x2d, 0x48, 0xa1, 0x08, 0xd8, 0x75, 0x3c, 0x40, 0x53,
+ 0x59, 0x5a, 0x04, 0xf6, 0xcb, 0xd6, 0x2f, 0x51, 0x9e, 0x4a,
+ 0xa8, 0xef, 0x02, 0x3c, 0x61, 0xa8, 0x45, 0x5a, 0x99, 0x9a,
+ 0x73, 0x09, 0xc0, 0x71, 0x6f, 0x4d, 0xb7, 0x2c, 0x69, 0x6c,
+ 0x1e, 0x85, 0x48, 0xea, 0x33, 0x5b, 0xb1, 0x28, 0x09, 0xe3,
+ 0x24, 0x47, 0x7a, 0x12, 0xd8, 0x77, 0xa0, 0xfd, 0x3c, 0xcd,
+ 0x23, 0x53, 0x61, 0xf0, 0x9e, 0x49, 0x44, 0x19, 0x9c, 0xe1,
+ 0x0a, 0xb6, 0xc5, 0x93, 0x35, 0x4d, 0x06, 0xde, 0xb5, 0xac,
+ 0x15, 0xe2, 0x8d, 0x47, 0x6a, 0x8a, 0x29, 0x58, 0x8f, 0xfc,
+ 0x2b, 0x32, 0xc4, 0x0e, 0x4e, 0x75, 0xb2, 0x71, 0x44, 0x91,
+ 0xb6, 0xfb, 0x50, 0x60, 0xc4, 0x50, 0x4d, 0xd9, 0x1c, 0xd7,
+ 0x28, 0xb5, 0x2e, 0x08, 0x82, 0xfd, 0xbd, 0x7c, 0x48, 0x62,
+ 0x00, 0x01, 0x30, 0xa5, 0x47, 0x2a, 0x10, 0x4b, 0xfe, 0x3e,
+ 0xb5, 0xd7, 0xe2, 0x1e, 0x5a, 0xd0, 0xe3, 0x6b, 0xa0, 0x9c,
+ 0xe3, 0x12, 0x05, 0xb9, 0x1c, 0x7b, 0x3f, 0x18, 0x88, 0x11,
+ 0xc3, 0x4d, 0xe8, 0x83, 0x78, 0xf1, 0x8c, 0x6a, 0xb3, 0x87,
+ 0x7f, 0x67, 0xdf, 0x47, 0xc4, 0x2f, 0xea, 0x9c, 0x79, 0x98,
+ 0x35, 0x2d, 0x7b, 0x2d, 0xc7, 0x3e, 0x31, 0x13, 0x6e, 0xf3,
+ 0xfd, 0xa8, 0x16, 0x25, 0x03, 0x3f, 0xb1, 0x14, 0x77, 0xff,
+ 0xa1, 0xf2, 0xe9, 0x98, 0x32, 0xe4, 0x2f, 0x3e, 0xb0, 0x6e,
+ 0x29, 0x84, 0x0f, 0xdd, 0x85, 0xbb, 0xda, 0x0a, 0xc6, 0x19,
+ 0x78, 0x9a, 0x6b, 0xdd, 0x8b, 0x40, 0x39, 0x1f, 0x19, 0xce,
+ 0xad, 0x79, 0xc9, 0xda, 0x59, 0xbf, 0x6e, 0xbc, 0xb0, 0xc8,
+ 0x6e, 0x61, 0xeb, 0x2e, 0xba, 0xa6, 0x50, 0x06, 0x8c, 0x74,
+ 0xab, 0x23, 0x4f, 0x9d, 0x9e, 0x20, 0x67, 0x9a, 0x0c, 0xea,
+ 0x60, 0x14, 0x85, 0x42, 0xc4, 0x7d, 0xc0, 0x1c, 0x62, 0xa5,
+ 0x7e, 0x65, 0x1d, 0xee, 0xa1, 0xfd, 0xed, 0x82, 0x19, 0x1e,
+ 0x90, 0x87, 0xab, 0xdb, 0x80, 0xb7, 0xb2, 0xab, 0xa0, 0xbc,
+ 0xdb, 0x99, 0x9b, 0x2e, 0x8e, 0xc4, 0x96, 0xfe, 0x31, 0x82,
+ 0x9e, 0xe9, 0xc9, 0xa3, 0xd0, 0xe3, 0x52, 0x0f, 0x73, 0x24,
+ 0xd7, 0xb6, 0x41, 0xcc, 0xfc, 0xae, 0x86, 0xb5, 0x79, 0xe5,
+ 0x25, 0xc8, 0x76, 0x60, 0x48, 0x3c, 0xa4, 0x74, 0x6a, 0x7b,
+ 0x7e, 0xa8, 0x5b, 0xf8, 0x5b, 0x79, 0x0f, 0x19, 0x95, 0x7d,
+ 0x7f, 0x56, 0x31, 0xcc, 0x34, 0xd0, 0xb8, 0x0c, 0x29, 0x3f,
+ 0x83, 0x54, 0x02, 0x75, 0xe3, 0x79, 0xb6, 0x64, 0xf9, 0x66,
+ 0x7f, 0x90, 0xe1, 0xa7, 0x7b, 0xfa, 0x17, 0x68, 0x07, 0xd1,
+ 0xef, 0x35, 0x52, 0xf3, 0x56, 0xde, 0x35, 0x35, 0x14, 0x1b,
+ 0xcd, 0x97, 0x1a, 0xe5, 0xf4, 0xb5, 0xae, 0xfc, 0x53, 0x0c,
+ 0xec, 0x36, 0xf0, 0xb4, 0x30, 0xea, 0x9b, 0xde, 0x60, 0x31,
+ 0x72, 0x7e, 0x99, 0x41, 0x78, 0x90, 0x81, 0x73, 0x0a, 0x81,
+ 0xe2, 0xaa, 0x70, 0x68, 0x67, 0x7a, 0x52, 0x4a, 0x4d, 0xd4,
+ 0x9f, 0x90, 0x0d, 0xd9, 0x3a, 0x1f, 0x8d, 0x80, 0xfa, 0x68,
+ 0x62, 0x54, 0xae, 0xb7, 0x0f, 0x3a, 0xfa, 0xed, 0xc9, 0xce,
+ 0x20, 0x8d, 0x88, 0x48, 0x79, 0x82, 0x8a, 0xf6, 0xc5, 0x4d,
+ 0x6e, 0xed, 0x38, 0x81, 0x75, 0x65, 0xac, 0xf6, 0x72, 0xee,
+ 0xf0, 0x2e, 0x6d, 0x3f, 0x2e, 0x2a, 0x54, 0xf2, 0xf1, 0x5a,
+ 0xb9, 0x81, 0xce, 0x56, 0xbf, 0xed, 0xab, 0xd5, 0x32, 0xf1,
+ 0x95, 0x86, 0x81, 0xf9, 0xfd, 0x38, 0x1d, 0xc7, 0x9f, 0x55,
+ 0xe1, 0x2c, 0xd1, 0x28, 0x66, 0x26, 0x81, 0xf5, 0x12, 0x72,
+ 0x02, 0x35, 0x87, 0x65, 0x01, 0xea, 0x4f, 0xf7, 0x95, 0xba,
+ 0xb1, 0x09, 0x91, 0x9c, 0x89, 0xb4, 0x48, 0x1d, 0x7b, 0xb4,
+ 0x37, 0x76, 0x6c, 0xce, 0x83, 0x0e, 0x2c, 0xb8, 0xd5, 0xe0,
+ 0x43, 0x36, 0x3c, 0x2d, 0xb1, 0x0f, 0x29, 0x4e, 0x1d, 0x36,
+ 0x58, 0x9a, 0xcd, 0xf4, 0xa7, 0xad, 0xae, 0xdb, 0x8f, 0x59,
+ 0xd0, 0x78, 0xb4, 0xcb, 0xfd, 0xe6, 0x1b, 0xa9, 0x24, 0x26,
+ 0xb7, 0xa2, 0xc0, 0xbd, 0x08, 0x53, 0xd2, 0x4e, 0xb2, 0x19,
+ 0x90, 0xb1, 0xb0, 0xa7, 0x57, 0xaa, 0xeb, 0x11, 0x71, 0xdd,
+ 0x3b, 0xae, 0x04, 0xf4, 0x44, 0xa5, 0x27, 0xeb, 0xb3, 0x2e,
+ 0xdd, 0x70, 0x7e, 0x0d, 0x2b, 0x2f, 0xc4, 0x4c, 0xee, 0xff,
+ 0x4d, 0x94, 0xde, 0x6f, 0x48, 0xa1, 0x7b, 0xa2, 0xa2, 0xef,
+ 0x3a, 0xa3, 0x0c, 0x99, 0x4e, 0x6e, 0xd4, 0x64, 0xbb, 0xd2,
+ 0xa0, 0x38, 0x39, 0xe8, 0x16, 0xa3, 0x4b, 0x77, 0xb7, 0xa4,
+ 0xf6, 0xc0, 0x12, 0xec, 0xfe, 0x97, 0xc0, 0xa4, 0xfc, 0x90,
+ 0x8c, 0x07, 0xfb, 0x0a, 0xc9, 0x4f, 0xe8, 0xf7, 0x6e, 0x45,
+ 0xf6, 0xab, 0x8b, 0xb1, 0x58, 0x56, 0x5b, 0xda, 0x12, 0xc0,
+ 0x4c, 0xe1, 0x74, 0x1c, 0xb9, 0x14, 0xdf, 0xe4, 0x17, 0x53,
+ 0xb3, 0x0c, 0xe2, 0xe1, 0xbc, 0xfc, 0xe6, 0x9c, 0xed, 0xed,
+ 0x82, 0x20, 0x2a, 0x4d, 0xdf, 0xd8, 0x78, 0x6e, 0xf5, 0x16,
+ 0x57, 0x48, 0x58, 0x9a, 0xb6, 0x57, 0x20, 0xf0, 0x74, 0xcc,
+ 0xcf, 0xdd, 0x5c, 0xf4, 0xd6, 0x7e, 0xc4, 0x53, 0x13, 0xa4,
+ 0xf1, 0x68, 0xc5, 0xa4, 0xe1, 0x91, 0x58, 0xcf, 0x5c, 0x9f,
+ 0x36, 0xbf, 0x20, 0x21, 0x1b, 0x34, 0x5c, 0xbd, 0x2c, 0x64,
+ 0xc5, 0x6f, 0x7f, 0xb9, 0x91, 0x65, 0xcf, 0x25, 0x44, 0x93,
+ 0x99, 0x1f, 0xb9, 0x82, 0xeb, 0xc5, 0x3b, 0x4f, 0xd2, 0xad,
+ 0x5e, 0xaf, 0x69, 0xfc, 0x2f, 0x54, 0xe4, 0xd0, 0x34, 0xdb,
+ 0x47, 0xef, 0x8b, 0x16, 0x54, 0x2b, 0x67, 0x27, 0x3c, 0x50,
+ 0x9f, 0xf0, 0x6c, 0xe6, 0xe2, 0x08, 0xd0, 0x0a, 0x36, 0x76,
+ 0xc1, 0x65, 0x62, 0x01, 0x4d, 0x70, 0xb8, 0x42, 0xe4, 0x88,
+ 0xee, 0x63, 0x0b, 0x74, 0x64, 0x2b, 0x15, 0xf1, 0x90, 0x6c,
+ 0xa3, 0x3f, 0xf4, 0x94, 0xca, 0x3f, 0x48, 0x64, 0x5a, 0x09,
+ 0x36, 0x5c, 0x7c, 0x4f, 0x75, 0xf0, 0x4e, 0xe3, 0x3d, 0xf3,
+ 0x6d, 0x9b, 0x4f, 0x99, 0x29, 0xf1, 0xe2, 0xff, 0x92, 0x38,
+ 0x5d, 0xf6, 0xf3, 0xd2, 0xc5, 0x5d, 0x1d, 0x89, 0x78, 0x09,
+ 0xbb, 0x6a, 0x3a, 0x0a, 0x9c, 0x01, 0x4c, 0xd9, 0xfd, 0x55,
+ 0x48, 0xa1, 0xf7, 0xa1, 0x9c, 0x55, 0xc1, 0x31, 0xaf, 0xa5,
+ 0x03, 0xbb, 0xba, 0x8c, 0x76, 0x0a, 0xf0, 0xd6, 0x03, 0x57,
+ 0x71, 0xf5, 0xfe, 0xad, 0x7f, 0x36, 0x23, 0x57, 0xba, 0x9b,
+ 0x20, 0x92, 0x14, 0x9e, 0xcf, 0x76, 0x59, 0xcd, 0xe2, 0x47,
+ 0x12, 0xb4, 0x1e, 0xb5, 0x52, 0xc3, 0x7f, 0xda, 0xa0, 0x35,
+ 0xc2, 0x55, 0x56, 0x97, 0xd0, 0x57, 0xa0, 0xcc, 0xcd, 0x27,
+ 0xcc, 0x08, 0x1c, 0x9c, 0x06, 0xfd, 0xbe, 0x47, 0xdf, 0x78,
+ 0x08, 0x17, 0xb6, 0x7f, 0x31, 0x1e, 0x0a, 0xdc, 0x54, 0xcc,
+ 0x06, 0xf7, 0xbf, 0xee, 0x74, 0x48, 0x21, 0x39, 0x6c, 0xf0,
+ 0x1f, 0x29, 0x19, 0x76, 0x19, 0x7f, 0xc2, 0x38, 0x94, 0x8a,
+ 0xe4, 0x2d, 0x7f, 0xff, 0xf7, 0x45, 0x57, 0x21, 0x98, 0xaa,
+ 0x65, 0x35, 0xba, 0x3c, 0x56, 0xff, 0x62, 0x88, 0xb9, 0xe8,
+ 0xe9, 0xdb, 0xd1, 0xf8, 0x59, 0x0f, 0x50, 0x13, 0x8b, 0xc2,
+ 0xc2, 0xbd, 0xba, 0x44, 0x44, 0x36, 0xe1, 0xb0, 0x13, 0x32,
+ 0x3a, 0x68, 0x4d, 0x62, 0xa8, 0x50, 0x87, 0x44, 0xbb, 0x31,
+ 0xfb, 0xe3, 0x25, 0x73, 0xa0, 0xf1, 0x2c, 0xf5, 0x34, 0x19,
+ 0x09, 0x66, 0xa9, 0x46, 0x21, 0xc7, 0xa2, 0x91, 0x7d, 0xe9,
+ 0xf4, 0x9d, 0xa9, 0x82, 0x28, 0xf4, 0x1f, 0x28, 0xa8, 0xa9,
+ 0x01, 0xf1, 0x97, 0x70, 0x9d, 0x61, 0x72, 0xd5, 0x8b, 0x84,
+ 0xcf, 0xf1, 0x7c, 0xc2, 0x7d, 0xa7, 0xa3, 0x25, 0x4f, 0x68,
+ 0x3f, 0x5e, 0x8e, 0xb6, 0x16, 0x18, 0x78, 0xcf, 0x63, 0x53,
+ 0x57, 0x08, 0x11, 0x55, 0x65, 0x1b, 0x55, 0x5e, 0x8d, 0xce,
+ 0x68, 0x2d, 0x77, 0x16, 0x1e, 0x87, 0xa6, 0xd0, 0x11, 0x44,
+ 0x37, 0x8f, 0xa2, 0x8b, 0xb0, 0xdb, 0x4b, 0x01, 0x2f, 0xd7,
+ 0x1e, 0x16, 0x93, 0xb5, 0x19, 0xad, 0xb7, 0xde, 0x13, 0x0d,
+ 0xac, 0xcd, 0x77, 0x6a, 0x15, 0x61, 0xeb, 0xe0, 0xd1, 0xbf,
+ 0xcf, 0x69, 0x10, 0x28, 0x72, 0x58, 0xfe, 0xc7, 0xce, 0xbc,
+ 0x92, 0xfd, 0x5f, 0x9c, 0xd6, 0x6d, 0x56, 0xbd, 0x39, 0x35,
+ 0xe0, 0xa6, 0x73, 0x19, 0x45, 0x6a, 0xf2, 0x36, 0x8a, 0x0a,
+ 0x58, 0xe1, 0x23, 0x07, 0x28, 0xa4, 0x89, 0x7a, 0x51, 0xbb,
+ 0xba, 0xd6, 0x42, 0x78, 0xbb, 0x5f, 0x42, 0x0b, 0xc0, 0x8f,
+ 0xc9, 0x3c, 0xc3, 0x7e, 0x41, 0x22, 0x94, 0x3c, 0x02, 0xd3,
+ 0x2c, 0xec, 0xf8, 0x8f, 0x14, 0x62, 0x7c, 0x78, 0x47, 0x73,
+ 0xe8, 0xd5, 0x40, 0x2d, 0x26, 0x82, 0x72, 0x16, 0xd7, 0xdd,
+ 0x9e, 0x8d, 0x8e, 0xfb, 0xaf, 0x83, 0x73, 0xd8, 0x4d, 0x85,
+ 0x28, 0x53, 0x72, 0xc1, 0xb0, 0x08, 0x5a, 0xc6, 0x6e, 0xbe,
+ 0xf9, 0x21, 0xdd, 0x8c, 0x30, 0xf5, 0x5e, 0xbe, 0xec, 0xcc,
+ 0xd2, 0x8a, 0x1a, 0xc3, 0xf4, 0x83, 0x98, 0x86, 0x3b, 0x4e,
+ 0x6d, 0x3e, 0x9e, 0xbe, 0x84, 0x80, 0x2f, 0x27, 0xbb, 0x6a,
+ 0x3e, 0x51, 0x18, 0xb0, 0x7a, 0x82, 0x82, 0x02, 0x53, 0x6d,
+ 0x2d, 0x2a, 0xab, 0x4a, 0x2e, 0x3b, 0xa4, 0x88, 0x90, 0x99,
+ 0x74, 0x10, 0x27, 0xa7, 0xe9, 0x5c, 0x52, 0x5b, 0xf5, 0xb0,
+ 0x00, 0xc7, 0x52, 0x91, 0x04, 0xc7, 0xff, 0x01, 0xb7, 0x56,
+ 0x40, 0x9f, 0x56, 0x54, 0xde, 0x96, 0x65, 0x61, 0x76, 0x06,
+ 0xb6, 0xdb, 0x16, 0xc8, 0x1c, 0x0b, 0xff, 0xf0, 0x73, 0xd0,
+ 0x89, 0x22, 0x86, 0xc6, 0xac, 0xc5, 0xb1, 0x98, 0x21, 0xd0,
+ 0x6c, 0x30, 0x92, 0x43, 0xb4, 0xa9, 0x80, 0xc8, 0xdd, 0x6f,
+ 0xf0, 0x4b, 0x57, 0x80, 0x6d, 0x53, 0xfb, 0xd4, 0x95, 0x39,
+ 0xde, 0x6e, 0xf5, 0x28, 0x9e, 0x92, 0x94, 0x42, 0x99, 0x42,
+ 0x4d, 0x02, 0x0a, 0x7b, 0x9b, 0x8f, 0xc6, 0x32, 0xb0, 0xd9,
+ 0x5d, 0x81, 0x74, 0xd2, 0x76, 0xdb, 0x64, 0x21, 0xdf, 0xc2,
+ 0xee, 0x5f, 0x77, 0x88, 0x13, 0x60, 0x32, 0xd8, 0x97, 0xf4,
+ 0x1c, 0x77, 0xe0, 0x49, 0xce, 0x1d, 0xa2, 0xbb, 0x66, 0xca,
+ 0x27, 0xfd, 0xc1, 0x96, 0x3a, 0x50, 0x4f, 0x1f, 0xbb, 0x56,
+ 0x24, 0x85, 0x76, 0x77, 0xfd, 0x84, 0x7b, 0xb4, 0x3c, 0x87,
+ 0x01, 0x09, 0x89, 0x4c, 0x0f, 0x8e, 0x44, 0xc0, 0x49, 0x39,
+ 0x49, 0x8b, 0x93, 0x09, 0xa6, 0x8d, 0x93, 0xf1, 0x5a, 0x3f,
+ 0x1f, 0x64, 0x2f, 0xd2, 0xe2, 0xbe, 0x99, 0x38, 0x38, 0xf1,
+ 0xec, 0x15, 0x46, 0xe3, 0x8a, 0x95, 0xe6, 0x3c, 0xf8, 0xa3,
+ 0x38, 0xb8, 0xc0, 0x20, 0x5f, 0xcb, 0x36, 0xe4, 0x90, 0x79,
+ 0x0c, 0x8e, 0xb6, 0xe5, 0x48, 0x23, 0x2d, 0xcb, 0x3c, 0x88,
+ 0x31, 0x35, 0xe7, 0x8e, 0xfa, 0xf5, 0x81, 0x36, 0x35, 0x96,
+ 0x98, 0x39, 0xb0, 0x2d, 0xa3, 0xd7, 0x84, 0x3a, 0x92, 0x30,
+ 0x07, 0x9b, 0x70, 0x61, 0xda, 0xd6, 0x80, 0x94, 0x7b, 0x93,
+ 0x01, 0x10, 0xe2, 0x03, 0x23, 0x83, 0xc2, 0xef, 0x2f, 0x4e,
+ 0x31, 0x01, 0x87, 0x0c, 0x0b, 0x0c, 0x1f, 0x20, 0xaa, 0x75,
+ 0x52, 0xb5, 0xbc, 0xa2, 0x55, 0xd6, 0x06, 0x3f, 0xf1, 0x6a,
+ 0x1e, 0xa4, 0x00, 0x43, 0xdb, 0x0f, 0x9a, 0xd4, 0xc4, 0x45,
+ 0x2f, 0x20, 0x94, 0x57, 0xbc, 0x6e, 0xeb, 0x63, 0xda, 0x90,
+ 0xb2, 0x56, 0x9b, 0x6c, 0x81, 0xb3, 0x15, 0xd3, 0x1d, 0xe1,
+ 0xe4, 0x75, 0xaa, 0x3b, 0xbf, 0x45, 0x46, 0xd7, 0x0d, 0x31,
+ 0xb1, 0xf5, 0x8b, 0xfb, 0x5b, 0x3e, 0x48, 0x4b, 0xea, 0x09,
+ 0x0c, 0x82, 0xd3, 0x7a, 0x45, 0x1c, 0xc2, 0x4d, 0xda, 0x4d,
+ 0x08, 0xd1, 0x5d, 0x7c, 0xb5, 0x50, 0x24, 0xbf, 0x3b, 0x5a,
+ 0xb7, 0xca, 0x76, 0xc4, 0xee, 0x64, 0xf7, 0xc7, 0x77, 0x2d,
+ 0x06, 0x8d, 0x9b, 0xee, 0x7c, 0x6b, 0xf5, 0x6b, 0x8f, 0x62,
+ 0x4d, 0x37, 0x66, 0x31, 0x5f, 0xde, 0x2b, 0xb2, 0xe4, 0x70,
+ 0xda, 0xeb, 0x49, 0xe4, 0x1b, 0x1f, 0xdf, 0x45, 0xee, 0x5a,
+ 0x69, 0x68, 0x59, 0xe5, 0x71, 0x14, 0xcc, 0x4e, 0x73, 0xa4,
+ 0x20, 0x38, 0x5c, 0xc7, 0xa7, 0x28, 0xd5, 0xa0, 0xbf, 0xfd,
+ 0x6a, 0x0f, 0x92, 0x6f, 0x02, 0x64, 0x49, 0x19, 0xe4, 0x18,
+ 0x23, 0xa2, 0xda, 0x9b, 0x75, 0x7b, 0xc8, 0xdb, 0x8f, 0x78,
+ 0x68, 0x4c, 0x67, 0x47, 0x82, 0xf6, 0x10, 0x44, 0x23, 0x36,
+ 0x74, 0x02, 0x58, 0xad, 0x75, 0xd4, 0xad, 0x59, 0x9e, 0x2e,
+ 0x99, 0x67, 0xc2, 0x5b, 0x89, 0x91, 0x01, 0x37, 0x2e, 0xfe,
+ 0x50, 0xc4, 0x6d, 0xe7, 0x8b, 0x34, 0x53, 0x22, 0xd0, 0xc3,
+ 0x1f, 0x16, 0xc9, 0x14, 0x2f, 0x0b, 0x76, 0xdf, 0x29, 0xb9,
+ 0x32, 0xd4, 0xcd, 0x5e, 0x04, 0xdb, 0xf5, 0x47, 0x3f, 0x99,
+ 0x26, 0xcb, 0x6b, 0xac, 0xae, 0x2b, 0xbc, 0x75, 0xf2, 0x8d,
+ 0x64, 0xff, 0xd2, 0x2b, 0xfb, 0x8c, 0x81, 0x79, 0xed, 0x8d,
+ 0x86, 0x89, 0xc7, 0xfd, 0xe8, 0xde, 0x1a, 0xe1, 0x96, 0x30,
+ 0xba, 0xe3, 0x8d, 0xc8, 0x43, 0xd5, 0x97, 0x52, 0xe5, 0x19,
+ 0x0b, 0xd4, 0xba, 0x41, 0xea, 0x9a, 0xad, 0xcc, 0x94, 0x24,
+ 0x6e, 0x9c, 0x8f, 0xd1, 0x69, 0x6b, 0x4a, 0x50, 0x92, 0xba,
+ 0x73, 0x41, 0xb1, 0xd1, 0x82, 0x10, 0xbb, 0xcf, 0x3c, 0x4b,
+ 0xdf, 0x49, 0xdc, 0xdf, 0xd3, 0xbb, 0xa6, 0xac, 0x63, 0xbc,
+ 0x88, 0x6a, 0x4f, 0x3b, 0xfa, 0xd4, 0xdd, 0xd4, 0x3c, 0x3a,
+ 0xe5, 0x68, 0xd7, 0x86, 0xbd, 0xcd, 0xd3, 0x6d, 0xa8, 0x22,
+ 0xca, 0x71, 0xd6, 0xd0, 0xa7, 0xa7, 0x75, 0xd2, 0x70, 0xd4,
+ 0xb4, 0x27, 0x78, 0x2b, 0xcd, 0x16, 0x10, 0x81, 0xb1, 0x59,
+ 0xfe, 0xe4, 0x1b, 0x62, 0xd1, 0xcf, 0x63, 0x7c, 0x72, 0x78,
+ 0xc2, 0x96, 0xaf, 0x88, 0x76, 0x28, 0x0a, 0x47, 0xeb, 0xa8,
+ 0xd9, 0xd1, 0xef, 0xea, 0x01, 0x20, 0x15, 0x63, 0x89, 0xb4,
+ 0xe7, 0x99, 0xa0, 0x3b, 0x2f, 0xdf, 0x99, 0x0b, 0xc3, 0xef,
+ 0xa6, 0xf3, 0xe0, 0x90, 0xc9, 0x82, 0x4c, 0x3e, 0x51, 0xe9,
+ 0x2c, 0x86, 0x7d, 0x31, 0x7e, 0x8f, 0x5d, 0x7a, 0x24, 0xff,
+ 0x65, 0xa9, 0xb6, 0xbc, 0x18, 0xc8, 0x28, 0x4c, 0x8d, 0xda,
+ 0xc7, 0x98, 0x6a, 0x54, 0xad, 0x22, 0x19, 0xca, 0x3d, 0x4d,
+ 0x88, 0xcc, 0x4e, 0x5c, 0x2a, 0xef, 0xd0, 0x60, 0x3c, 0xe3,
+ 0x49, 0xd2, 0xf6, 0x59, 0x38, 0x25, 0xed, 0x31, 0x52, 0x18,
+ 0xb4, 0x31, 0x31, 0x43, 0x8f, 0x85, 0x7d, 0x59, 0xa8, 0xd2,
+ 0x26, 0x4c, 0xa0, 0xfb, 0xf8, 0xdb, 0x6a, 0x12, 0xad, 0x40,
+ 0x73, 0xb1, 0x5d, 0x61, 0xf5, 0x49, 0x65, 0x7b, 0x44, 0x30,
+ 0x49, 0x7a, 0xd7, 0xe9, 0xce, 0x34, 0x9c, 0x64, 0x6e, 0x31,
+ 0xae, 0x48, 0xad, 0x4c, 0x69, 0xc4, 0xd9, 0xf8, 0x2a, 0xb0,
+ 0x7e, 0xb3, 0xba, 0xec, 0xea, 0x2b, 0x49, 0x09, 0x21, 0x53,
+ 0x63, 0xb7, 0xc6, 0xd6, 0x80, 0x9c, 0x77, 0x27, 0xe9, 0xf6,
+ 0xc7, 0x06, 0x2c, 0x8e, 0xc1, 0xb8, 0x31, 0x15, 0xa9, 0x87,
+ 0x13, 0xfd, 0xfd, 0x32, 0x18, 0xdd, 0x5c, 0xba, 0xf3, 0x70,
+ 0x94, 0x09, 0x9d, 0xb6, 0xbc, 0x2c, 0x91, 0xef, 0x42, 0xb7,
+ 0x13, 0x25, 0x90, 0x77, 0x10, 0xdd, 0x80, 0xa9, 0xd9, 0xab,
+ 0xef, 0xcf, 0xbe, 0x75, 0x93, 0xe2, 0xbc, 0xe7, 0x26, 0xdf,
+ 0x77, 0x88, 0xe0, 0x90, 0xc0, 0x2d, 0x20, 0xc9, 0x8c, 0xfa,
+ 0x82, 0x91, 0x57, 0xfb, 0xb1, 0xec, 0xdf, 0x2b, 0x2f, 0x9b,
+ 0x6a, 0x23, 0xa0, 0x3e, 0x2a, 0xa3, 0x39, 0x8c, 0xea, 0x09,
+ 0x06, 0x03, 0x7b, 0xa8, 0xe7, 0x77, 0x32, 0x21, 0x1a, 0x29,
+ 0xcd, 0x22, 0x60, 0x9b, 0xc0, 0xfe, 0xc2, 0xf3, 0x9c, 0x83,
+ 0x56, 0x8f, 0xf0, 0x22, 0xad, 0xfd, 0xe3, 0xa9, 0xe3, 0xc5,
+ 0xbf, 0xd1, 0xf3, 0xbd, 0xc6, 0x43, 0xc3, 0x84, 0xf2, 0x46,
+ 0x91, 0x5b, 0x37, 0x43, 0xb9, 0xcf, 0xe4, 0x47, 0x7a, 0x5a,
+ 0xfd, 0xd2, 0xc0, 0xb6, 0x7e, 0x99, 0xea, 0x15, 0x1b, 0x31,
+ 0x3c, 0x14, 0x6b, 0xbf, 0x26, 0x4f, 0xfa, 0x38, 0x39, 0xef,
+ 0x75, 0x4c, 0xda, 0x4c, 0x4f, 0x00, 0xef, 0x0f, 0xc6, 0x3b,
+ 0x5b, 0x4c, 0xf1, 0x1b, 0x35, 0x92, 0xb9, 0x74, 0x9e, 0x0f,
+ 0x16, 0x2a, 0x2b, 0x3c, 0xa0, 0x80, 0xf0, 0xe5, 0x56, 0xaa,
+ 0xdb, 0x0e, 0x4f, 0xfc, 0x5f, 0xb2, 0xd1, 0x0b, 0xe2, 0x04,
+ 0x1e, 0xe8, 0x6f, 0x4f, 0x81, 0xdb, 0xc7, 0x1a, 0x0c, 0xb0,
+ 0x00, 0xed, 0x8a, 0x7b, 0x1d, 0x44, 0xde, 0x30, 0xfa, 0xb6,
+ 0x88, 0x3b, 0x73, 0x81, 0xb3, 0x57, 0x31, 0x38, 0x0e, 0x13,
+ 0x77, 0x9e, 0x5a, 0x4d, 0xa1, 0x93, 0x73, 0x30, 0x0d, 0x44,
+ 0x56, 0x97, 0x88, 0xbf, 0xc4, 0x36, 0xe9, 0xec, 0xe2, 0x58,
+ 0x11, 0x39, 0x33, 0x71, 0x6b, 0xd4, 0xb7, 0x4a, 0x9a, 0x2c,
+ 0xc0, 0xac, 0x88, 0xeb, 0x08, 0x8c, 0x43, 0x48, 0xa2, 0xba,
+ 0xfa, 0x98, 0x95, 0x92, 0x2d, 0xa2, 0xee, 0x72, 0xb3, 0x7e,
+ 0xd9, 0x11, 0x1a, 0xa3, 0x41, 0xc4, 0x65, 0x19, 0x1d, 0xdb,
+ 0x92, 0xf4, 0x3e, 0xf4, 0xaa, 0x49, 0xad, 0x77, 0xc6, 0x03,
+ 0xdd, 0x19, 0x7a, 0x8d, 0x62, 0x09, 0x19, 0xd7, 0xf7, 0xd3,
+ 0x84, 0xf1, 0xcb, 0x45, 0x54, 0xd1, 0x74, 0x1e, 0xf8, 0xe6,
+ 0xa4, 0xfd, 0xaa, 0x30, 0xf8, 0xf9, 0xe1, 0x2b, 0xcf, 0x65,
+ 0x22, 0xf9, 0x73, 0xcf, 0x5d, 0xe3, 0xde, 0x93, 0x94, 0x84,
+ 0x8f, 0xe1, 0x66, 0x99, 0x6a, 0x71, 0x76, 0xae, 0xdb, 0x16,
+ 0x2b, 0x73, 0xab, 0x4e, 0x61, 0x08, 0xa0, 0xdc, 0x3f, 0xd4,
+ 0xe3, 0xac, 0x89, 0x7a, 0x31, 0xc1, 0xb9, 0x20, 0x92, 0x10,
+ 0x58, 0x1b, 0x87, 0xfd, 0xe3, 0x7c, 0x42, 0xc8, 0x26, 0x06,
+ 0x50, 0xce, 0xbb, 0x00, 0x0b, 0x9f, 0x09, 0xdc, 0xac, 0x31,
+ 0xe0, 0xcd, 0xa6, 0xee, 0x23, 0xef, 0x64, 0x87, 0x65, 0x5f,
+ 0xfb, 0x5f, 0x6c, 0x29, 0x4a, 0xf9, 0xd3, 0xaf, 0x4e, 0xaa,
+ 0x39, 0x30, 0x82, 0x05, 0xc6, 0x1c, 0x1e, 0x83, 0x8f, 0xe4,
+ 0xf7, 0x1c, 0xca, 0x3b, 0x03, 0x01, 0x80, 0xe4, 0x6a, 0x84,
+ 0x71, 0xd4, 0xe9, 0xa6, 0xba, 0x72, 0x89, 0x69, 0xfa, 0x56,
+ 0x6d, 0x26, 0xe3, 0x1d, 0xfa, 0x91, 0x8f, 0x4c, 0xcc, 0x50,
+ 0x74, 0x75, 0x2f, 0xe3, 0x97, 0x16, 0x07, 0x78, 0x40, 0xde,
+ 0x58, 0x22, 0xca, 0x41, 0xde, 0xb9, 0x92, 0x56, 0x55, 0x89,
+ 0x85, 0x20, 0x44, 0x5a, 0xd0, 0xfc, 0x88, 0x4e, 0x49, 0x81,
+ 0x32, 0xcf, 0x44, 0xb6, 0xee, 0x58, 0x0e, 0x22, 0xf8, 0x8f,
+ 0x05, 0x2a, 0x78, 0x30, 0x34, 0x11, 0xb0, 0x8f, 0x21, 0x10,
+ 0xb7, 0xe2, 0x80, 0xa2, 0xe0, 0x69, 0xbc, 0x29, 0xe3, 0x48,
+ 0x08, 0xa6, 0xdc, 0xbc, 0x68, 0xd7, 0xd7, 0x84, 0x23, 0x3f,
+ 0xb0, 0x0a, 0x69, 0xa1, 0x87, 0x13, 0x29, 0x0d, 0x71, 0x3d,
+ 0xad, 0xad, 0xf0, 0x5e, 0x53, 0x04, 0x03, 0x9e, 0x4b, 0xb2,
+ 0x24, 0xc2, 0x82, 0x8c, 0xef, 0x4c, 0x70, 0xcd, 0x58, 0xb3,
+ 0x19, 0xa4, 0xe8, 0x0e, 0xc0, 0xfd, 0x57, 0x2b, 0x65, 0xee,
+ 0xa5, 0x43, 0x02, 0xf2, 0xbe, 0xe7, 0x66, 0xc9, 0xb5, 0xfd,
+ 0x70, 0xc0, 0x93, 0x9b, 0xe8, 0x60, 0x74, 0xd4, 0xcc, 0xa5,
+ 0xa6, 0xa2, 0x66, 0x35, 0xdc, 0x74, 0xf3, 0x02, 0x16, 0x74,
+ 0x9a, 0x91, 0x0a, 0x3d, 0xa4, 0x8b, 0x98, 0xd1, 0xfd, 0x29,
+ 0x34, 0xea, 0x52, 0x89, 0x16, 0x62, 0xdd, 0xed, 0xb5, 0x8b,
+ 0xed, 0xe4, 0x03, 0xd9
+ };
+
+ static const uint8_t keys_a_ps5[0x70 * 0x20] = {
+ 0x7f, 0xae, 0x83, 0x20, 0x47, 0x39, 0xb6, 0x5b, 0x23, 0x9b,
+ 0x5c, 0xa5, 0x37, 0xc4, 0xdf, 0x59, 0x26, 0x81, 0x0a, 0x53,
+ 0xa1, 0xae, 0xa3, 0xa3, 0xf9, 0xbe, 0xaa, 0x9f, 0xca, 0xe0,
+ 0x9e, 0x45, 0x8d, 0x43, 0x7f, 0x53, 0x3e, 0xc0, 0x17, 0x90,
+ 0xd2, 0x03, 0x2d, 0x92, 0xa8, 0x1b, 0x3e, 0x17, 0x9a, 0x9d,
+ 0xc9, 0x45, 0x39, 0x9e, 0x6c, 0x5f, 0xb6, 0xb2, 0x42, 0x57,
+ 0x6a, 0x1a, 0xa0, 0x35, 0x89, 0xdd, 0x6a, 0xc6, 0xa9, 0x2d,
+ 0x06, 0xa2, 0xd3, 0x70, 0xd5, 0xa7, 0x91, 0x32, 0x42, 0x5a,
+ 0x44, 0x0f, 0x35, 0x86, 0x18, 0xc0, 0x75, 0x9f, 0x8e, 0x07,
+ 0xb6, 0x80, 0x46, 0xc1, 0xbc, 0xe3, 0x4f, 0xc0, 0xdc, 0xa1,
+ 0xaf, 0xfb, 0x1b, 0x2b, 0x4d, 0xb6, 0x14, 0x8f, 0xaf, 0xeb,
+ 0x49, 0xdd, 0x2a, 0xb3, 0xfd, 0xb2, 0x32, 0x86, 0x3c, 0xe7,
+ 0x1a, 0x96, 0x14, 0x9d, 0xb1, 0xd9, 0x90, 0x72, 0xb3, 0xb3,
+ 0x3d, 0x6f, 0x3d, 0xcc, 0x26, 0xbc, 0x51, 0xda, 0x91, 0x6e,
+ 0xc6, 0xa9, 0xf9, 0x9f, 0x86, 0x07, 0x13, 0x70, 0xdc, 0xe2,
+ 0x21, 0xd7, 0x4c, 0xb3, 0xc3, 0x98, 0x02, 0x94, 0x9a, 0x63,
+ 0x6d, 0x34, 0x73, 0x2d, 0xa1, 0x23, 0x83, 0xa8, 0x58, 0x34,
+ 0x3f, 0x30, 0x1b, 0x9b, 0x01, 0xfb, 0xfa, 0xe4, 0x02, 0xa3,
+ 0x52, 0x4d, 0xfa, 0xc8, 0x16, 0xb8, 0xfa, 0xc5, 0x67, 0xe1,
+ 0x89, 0x28, 0xe1, 0xe5, 0xf4, 0xd7, 0xa2, 0x9f, 0xcc, 0xd4,
+ 0x39, 0x91, 0x41, 0xe9, 0xa2, 0x7a, 0x44, 0xb4, 0xb6, 0x4b,
+ 0xe9, 0xd7, 0x2c, 0x0b, 0x13, 0xc5, 0xd9, 0xa4, 0x50, 0x0c,
+ 0x15, 0x94, 0xc8, 0x65, 0x88, 0xac, 0xe4, 0x14, 0x77, 0x23,
+ 0x04, 0x9e, 0x30, 0xec, 0x3f, 0x27, 0xe7, 0xc2, 0xb1, 0x76,
+ 0x15, 0xbb, 0x45, 0x44, 0x6f, 0x1b, 0xcd, 0x2b, 0xae, 0x07,
+ 0x77, 0x63, 0x1a, 0xde, 0x10, 0xad, 0xe6, 0xde, 0x8c, 0x54,
+ 0xe5, 0x98, 0xde, 0xcc, 0x3b, 0x8b, 0xe1, 0x52, 0xb2, 0x78,
+ 0xa4, 0xa6, 0xa8, 0x43, 0x0a, 0xdb, 0x6a, 0xd6, 0x97, 0xe4,
+ 0x1a, 0x51, 0x8a, 0x81, 0x9c, 0x7d, 0xb9, 0x35, 0x67, 0x7a,
+ 0xfa, 0xbf, 0x59, 0xce, 0x6c, 0x30, 0xd8, 0x00, 0x95, 0x49,
+ 0x73, 0x75, 0xc6, 0xfc, 0xb7, 0xf4, 0x93, 0x16, 0x18, 0xc9,
+ 0x1b, 0x6e, 0x71, 0x84, 0x30, 0x85, 0xe3, 0x00, 0x19, 0x43,
+ 0xcb, 0x04, 0x61, 0x58, 0x83, 0x91, 0x8b, 0xf8, 0x0a, 0x3d,
+ 0x4c, 0x47, 0x71, 0x3c, 0x34, 0xf0, 0xa1, 0xee, 0x5d, 0xea,
+ 0xc0, 0xb9, 0x68, 0xee, 0xa2, 0x61, 0xb9, 0xb4, 0x69, 0x12,
+ 0x09, 0x90, 0x74, 0x4b, 0xc4, 0xbc, 0xaf, 0x32, 0x6b, 0x9c,
+ 0x69, 0x72, 0x15, 0x3b, 0xb2, 0x5d, 0x2c, 0x59, 0x95, 0x2b,
+ 0x72, 0xc3, 0xae, 0x45, 0x4c, 0xda, 0xfa, 0x0c, 0x66, 0x9c,
+ 0xf8, 0x23, 0xf3, 0x74, 0x9d, 0xf9, 0x0e, 0x5c, 0x18, 0x22,
+ 0x02, 0x0f, 0x96, 0x0c, 0xe9, 0x9b, 0xb9, 0x05, 0xf5, 0xf9,
+ 0x38, 0xb7, 0xcd, 0x36, 0x03, 0xb0, 0x89, 0x48, 0x3e, 0x34,
+ 0xb5, 0xe6, 0x8f, 0xf0, 0xa7, 0x20, 0xad, 0x42, 0x0f, 0x55,
+ 0xbc, 0x16, 0x8c, 0x81, 0xab, 0xb7, 0x6d, 0x8b, 0x88, 0x88,
+ 0xde, 0x14, 0x59, 0x7b, 0xf4, 0x28, 0x8a, 0x58, 0x26, 0xbc,
+ 0xed, 0x03, 0x27, 0x7a, 0x59, 0x35, 0xdd, 0x0f, 0xff, 0x23,
+ 0xbb, 0x18, 0x93, 0x56, 0xff, 0x48, 0xd8, 0xd3, 0x71, 0xa1,
+ 0xc3, 0x61, 0x30, 0x83, 0xd4, 0x06, 0xcf, 0x41, 0xde, 0x81,
+ 0x7a, 0x10, 0x16, 0xce, 0xe7, 0x9f, 0xdc, 0x97, 0x24, 0xc3,
+ 0x62, 0x04, 0x42, 0xbb, 0xb7, 0xad, 0x13, 0x4e, 0x2b, 0x0c,
+ 0xf1, 0x36, 0x53, 0xb5, 0xa4, 0x14, 0xec, 0xdf, 0x8a, 0xa1,
+ 0x9e, 0xd3, 0x62, 0xd1, 0xe8, 0xc1, 0xa3, 0x9a, 0x87, 0x76,
+ 0x06, 0x0a, 0x84, 0x76, 0x7b, 0x88, 0xdc, 0xeb, 0x4a, 0x68,
+ 0xa4, 0x0f, 0x83, 0x62, 0xfd, 0xf5, 0x01, 0x50, 0x8b, 0xbc,
+ 0x6c, 0xd0, 0xe9, 0x60, 0x2c, 0xe5, 0xef, 0x88, 0xce, 0xc6,
+ 0x83, 0x4f, 0x7a, 0x32, 0xa3, 0xc0, 0x0e, 0x05, 0x83, 0x26,
+ 0x49, 0x3b, 0x88, 0xe5, 0xb9, 0x26, 0xc5, 0x8f, 0xbb, 0xe3,
+ 0x54, 0x05, 0x6b, 0x8d, 0x42, 0x56, 0x04, 0x01, 0xd8, 0x0b,
+ 0xe7, 0x83, 0x0a, 0x66, 0xb2, 0x94, 0xf1, 0x8c, 0x92, 0xb2,
+ 0xc3, 0xa4, 0x77, 0x1c, 0xb1, 0x5d, 0x24, 0x43, 0x70, 0x2e,
+ 0x1d, 0x43, 0xa5, 0x03, 0x27, 0x94, 0xf6, 0xaa, 0x06, 0xba,
+ 0xa0, 0x58, 0xf0, 0xc5, 0xc8, 0xe9, 0x80, 0x09, 0x98, 0xc9,
+ 0xc1, 0x95, 0xd2, 0x13, 0x33, 0xc8, 0xfb, 0x03, 0x5a, 0x37,
+ 0xa4, 0xdc, 0x44, 0xd7, 0x7e, 0x4b, 0x41, 0xcb, 0xb0, 0xac,
+ 0xda, 0x78, 0x80, 0x80, 0x6c, 0xb2, 0xdf, 0xe6, 0x5c, 0x4c,
+ 0x1e, 0x82, 0xd6, 0x76, 0x56, 0x21, 0xef, 0xd0, 0x6c, 0x0e,
+ 0xe1, 0x6a, 0xbb, 0x07, 0xfc, 0xbd, 0xa2, 0x34, 0x86, 0x5f,
+ 0xb0, 0x98, 0xb9, 0xa4, 0xc9, 0x85, 0xd4, 0xb0, 0xb1, 0x45,
+ 0xfc, 0x3c, 0x52, 0x17, 0x28, 0x5c, 0xe5, 0x10, 0xc7, 0x77,
+ 0x03, 0xba, 0x51, 0x89, 0x73, 0x6a, 0x28, 0x68, 0xb4, 0x01,
+ 0xb2, 0xf3, 0x6d, 0x8f, 0x41, 0xf2, 0x88, 0x19, 0x1a, 0x2a,
+ 0x86, 0xe2, 0xd6, 0xb5, 0x40, 0xbc, 0xf7, 0xd8, 0x6b, 0xe9,
+ 0xe2, 0x4f, 0x03, 0xae, 0x14, 0x7e, 0x5c, 0xfd, 0xa1, 0xf6,
+ 0xda, 0xa7, 0x6c, 0xeb, 0xe4, 0x01, 0xa0, 0x64, 0x9e, 0xb6,
+ 0xad, 0x31, 0xf6, 0xba, 0x1d, 0x88, 0x85, 0xfd, 0x62, 0x6c,
+ 0x4b, 0x10, 0xc3, 0x3d, 0x97, 0x10, 0xfa, 0x2f, 0x83, 0x23,
+ 0x55, 0xb0, 0x7d, 0xe6, 0x20, 0xe9, 0xae, 0x89, 0x10, 0xe4,
+ 0xfc, 0xfd, 0x82, 0xe4, 0x2b, 0x5a, 0x4e, 0xea, 0x17, 0xca,
+ 0xf8, 0x05, 0xba, 0x03, 0x9f, 0x06, 0x02, 0x3d, 0xb4, 0x9c,
+ 0x35, 0x27, 0xab, 0x47, 0xb1, 0x67, 0x78, 0x79, 0x4c, 0x6e,
+ 0xb1, 0x97, 0xb3, 0xb7, 0x7f, 0x54, 0xa7, 0x02, 0x55, 0x35,
+ 0x4d, 0x0f, 0x62, 0x64, 0x9b, 0xe6, 0x7b, 0xbb, 0x65, 0x5d,
+ 0x53, 0x21, 0x00, 0x7c, 0xe3, 0x9b, 0xf8, 0x68, 0xae, 0xbc,
+ 0x0d, 0x5f, 0xdc, 0xed, 0x4d, 0xfe, 0xd0, 0x2e, 0x6c, 0x93,
+ 0x0c, 0xef, 0x3d, 0x98, 0x74, 0x7b, 0x30, 0xdb, 0x6c, 0xa1,
+ 0x73, 0x20, 0x08, 0xf8, 0x79, 0xf9, 0x04, 0xe4, 0xa3, 0xaa,
+ 0xf8, 0x84, 0x50, 0x47, 0x9f, 0x05, 0x34, 0xb6, 0x18, 0x05,
+ 0xc5, 0x93, 0xb4, 0xb6, 0x07, 0x54, 0xbf, 0xff, 0x6b, 0x1f,
+ 0x4b, 0xd6, 0x5f, 0x90, 0x69, 0x5e, 0x12, 0xd9, 0x84, 0x48,
+ 0xf3, 0xb8, 0xe2, 0x95, 0x97, 0xc0, 0xaf, 0x4d, 0x68, 0x88,
+ 0x1d, 0xa2, 0xa1, 0x96, 0x0a, 0x32, 0xe7, 0x56, 0x6b, 0xc3,
+ 0x3c, 0xee, 0x89, 0x31, 0xd5, 0xf1, 0x23, 0x5c, 0xec, 0x45,
+ 0x3c, 0x55, 0x8f, 0xa6, 0x39, 0x08, 0xa8, 0xf2, 0x01, 0x28,
+ 0xd4, 0x97, 0x7f, 0x91, 0x63, 0x49, 0xe2, 0xc7, 0xf8, 0x7e,
+ 0xcb, 0x01, 0x1a, 0xdd, 0xe0, 0x57, 0xf8, 0x7f, 0x71, 0x3b,
+ 0x17, 0xd4, 0xd9, 0x57, 0x20, 0xed, 0x49, 0x4d, 0x8f, 0x57,
+ 0xc5, 0xaa, 0x65, 0x70, 0x9f, 0xf1, 0x2c, 0x74, 0x0d, 0x57,
+ 0x07, 0x16, 0x26, 0x74, 0x8e, 0x7a, 0x5e, 0x4f, 0x44, 0xab,
+ 0x77, 0xe9, 0x79, 0xe2, 0x98, 0xe5, 0xca, 0x64, 0x45, 0x7c,
+ 0xbf, 0x8c, 0x7c, 0x0d, 0xba, 0x71, 0xec, 0xbf, 0x92, 0x46,
+ 0x96, 0x75, 0x5e, 0x61, 0xe2, 0x7c, 0x49, 0x4f, 0xcf, 0xa1,
+ 0x9a, 0xc4, 0x3b, 0x99, 0x0b, 0x8a, 0xd2, 0x12, 0x33, 0x17,
+ 0x20, 0xa0, 0x76, 0x17, 0x23, 0x3f, 0x76, 0xac, 0xb4, 0xcc,
+ 0x07, 0x5a, 0x62, 0x22, 0x0b, 0x21, 0x27, 0xf6, 0x80, 0x3d,
+ 0x30, 0xdc, 0x8c, 0x2e, 0xf7, 0xf3, 0x96, 0xc0, 0xbf, 0x03,
+ 0x10, 0xd6, 0xbe, 0xa3, 0xe4, 0x19, 0x21, 0x28, 0x49, 0xe4,
+ 0xa7, 0xe6, 0x4e, 0x69, 0x79, 0x8f, 0x09, 0x55, 0x77, 0x5d,
+ 0x90, 0x5f, 0xfa, 0x83, 0x35, 0xc8, 0xab, 0xe3, 0xd8, 0x18,
+ 0x7c, 0x14, 0x8a, 0x73, 0x68, 0x37, 0xc2, 0xf2, 0xb6, 0x89,
+ 0xc0, 0x4b, 0xbf, 0x5c, 0xb3, 0x9e, 0x50, 0x84, 0x9a, 0x95,
+ 0xbf, 0x14, 0x95, 0x61, 0x9f, 0xb9, 0x40, 0xa9, 0x9c, 0xb2,
+ 0xf1, 0x3d, 0x1b, 0x34, 0xc9, 0x26, 0xbf, 0xb3, 0x0d, 0x2f,
+ 0x85, 0x6c, 0x48, 0xc0, 0x66, 0x56, 0xc3, 0x1a, 0x30, 0xea,
+ 0x58, 0x96, 0x15, 0xc9, 0xf8, 0x19, 0x7b, 0xa0, 0x07, 0xb3,
+ 0x01, 0xc8, 0xfc, 0xe0, 0xb6, 0xf4, 0x01, 0x7e, 0x58, 0x7e,
+ 0x08, 0x09, 0x48, 0x33, 0x86, 0x9a, 0xea, 0xb4, 0x48, 0x51,
+ 0xe8, 0x5e, 0x69, 0x20, 0x6a, 0x38, 0x77, 0x1b, 0x8a, 0xa5,
+ 0x43, 0xe4, 0x67, 0x14, 0x96, 0x18, 0xc4, 0x7e, 0x4b, 0xbd,
+ 0x90, 0xd6, 0x03, 0x43, 0xce, 0xdc, 0x85, 0x23, 0x68, 0x90,
+ 0x09, 0x72, 0xc9, 0x8d, 0x4d, 0x89, 0xe2, 0x48, 0x6d, 0x03,
+ 0x2f, 0x42, 0xe0, 0x1a, 0x5b, 0x96, 0x15, 0x16, 0x3a, 0x56,
+ 0xa9, 0xa9, 0xb1, 0xac, 0xf3, 0x96, 0x58, 0x6d, 0x61, 0x13,
+ 0x92, 0x79, 0xf1, 0x58, 0x0b, 0x56, 0x5b, 0x2f, 0xe5, 0x43,
+ 0x35, 0x82, 0x78, 0xce, 0x3c, 0x71, 0x6c, 0x91, 0xc7, 0xc9,
+ 0xd2, 0xbf, 0x30, 0xb6, 0x68, 0x87, 0x50, 0x06, 0x98, 0x54,
+ 0x51, 0x2b, 0x2e, 0x57, 0x15, 0x01, 0x1f, 0x85, 0x06, 0x5f,
+ 0xf0, 0xbe, 0x22, 0x09, 0x14, 0x2a, 0xbf, 0x34, 0x11, 0x9e,
+ 0x31, 0xa8, 0x7c, 0x9e, 0x17, 0x03, 0x3d, 0x0c, 0xe8, 0x21,
+ 0x0b, 0x8b, 0x61, 0x03, 0x33, 0x2e, 0xc2, 0x06, 0xc0, 0xc6,
+ 0x0c, 0xa4, 0xf1, 0x79, 0x18, 0x02, 0xde, 0xcf, 0x3d, 0x6c,
+ 0xf7, 0x5d, 0x3c, 0x81, 0x81, 0x38, 0x1a, 0x63, 0x90, 0x47,
+ 0xaa, 0x73, 0x25, 0xd9, 0x18, 0x3b, 0x88, 0xcd, 0xde, 0x06,
+ 0xd6, 0xbe, 0xcf, 0x2e, 0x20, 0x6a, 0x32, 0xbb, 0xf0, 0x12,
+ 0xfd, 0x3e, 0xa1, 0xbf, 0x58, 0x63, 0x8d, 0x0c, 0xaf, 0x1f,
+ 0x81, 0xc2, 0xb7, 0x03, 0xc2, 0xf7, 0x54, 0x81, 0x5b, 0x04,
+ 0x6e, 0x3e, 0x49, 0x5a, 0xba, 0xfa, 0x39, 0xec, 0xcf, 0xe8,
+ 0x12, 0x88, 0x30, 0x83, 0xa6, 0x2a, 0xd2, 0x08, 0xd5, 0xae,
+ 0x3c, 0x63, 0xb4, 0xd2, 0xd9, 0x09, 0x00, 0x47, 0x2a, 0xa3,
+ 0xae, 0xa6, 0x4e, 0x9e, 0xab, 0x65, 0x40, 0x32, 0xa4, 0x49,
+ 0xc7, 0xcc, 0x28, 0x0c, 0xe6, 0x60, 0xf8, 0x23, 0xd4, 0x8b,
+ 0xde, 0xd9, 0xbf, 0x10, 0x18, 0xf4, 0x39, 0x9f, 0x7e, 0x64,
+ 0x9e, 0xd4, 0x8c, 0x04, 0xe6, 0xec, 0xc6, 0x09, 0xc8, 0x2c,
+ 0xa4, 0xd4, 0xe2, 0x4b, 0x84, 0x1b, 0xc4, 0x04, 0x90, 0xdf,
+ 0xf4, 0x4c, 0x68, 0xe8, 0x32, 0xf6, 0xcc, 0xa0, 0x51, 0x48,
+ 0x92, 0x6c, 0x01, 0x9e, 0xb2, 0x24, 0x0b, 0x76, 0x76, 0x7d,
+ 0x2a, 0xb4, 0x68, 0x26, 0xe3, 0xae, 0x0d, 0xe5, 0x33, 0xf8,
+ 0x19, 0x86, 0x19, 0x4f, 0x5a, 0x14, 0xfb, 0x2f, 0x13, 0x83,
+ 0xce, 0x0a, 0x24, 0xcb, 0x42, 0x7a, 0xa6, 0x26, 0x6d, 0x52,
+ 0xd6, 0x9b, 0xab, 0x15, 0x36, 0x08, 0xbf, 0x75, 0x41, 0x16,
+ 0x75, 0x46, 0xb2, 0x59, 0xe0, 0xd2, 0x58, 0xf1, 0xc0, 0x25,
+ 0xf1, 0x11, 0x4a, 0x6c, 0x39, 0x3c, 0xce, 0xda, 0xec, 0x94,
+ 0xdc, 0xcb, 0x0a, 0xef, 0x5a, 0x5f, 0x68, 0x13, 0xc7, 0x75,
+ 0x0f, 0xdd, 0x7d, 0x80, 0x21, 0x6b, 0x32, 0xdc, 0x83, 0x8a,
+ 0xa4, 0xab, 0x1c, 0xc7, 0x03, 0x29, 0x3e, 0xee, 0xf3, 0x8b,
+ 0xc8, 0x5a, 0x9a, 0x30, 0x61, 0xc8, 0x1d, 0x51, 0xa8, 0x8b,
+ 0xd7, 0xa1, 0xf0, 0x6f, 0xe3, 0xec, 0x88, 0x11, 0x3b, 0x9d,
+ 0xbf, 0x64, 0xda, 0xed, 0x3e, 0x6d, 0xc6, 0xea, 0x36, 0xe1,
+ 0xbc, 0x54, 0x41, 0x2e, 0x4b, 0x38, 0xa5, 0x29, 0xab, 0xca,
+ 0x48, 0x53, 0x9e, 0x31, 0x05, 0x5d, 0x50, 0x78, 0xe5, 0x0d,
+ 0x2d, 0x87, 0x7c, 0x83, 0xf1, 0xe7, 0x18, 0xb2, 0x87, 0xf7,
+ 0x7f, 0x7f, 0x29, 0xd9, 0xe0, 0x1f, 0xc8, 0xa3, 0xc3, 0x3c,
+ 0x29, 0x7e, 0x02, 0x5d, 0x3e, 0x0c, 0xa3, 0xa2, 0x90, 0x6b,
+ 0xe9, 0x19, 0x57, 0x0a, 0x25, 0x6a, 0x19, 0xef, 0x5c, 0x38,
+ 0xab, 0x44, 0xed, 0x04, 0xaa, 0x58, 0x8e, 0xb7, 0xf6, 0x41,
+ 0x52, 0x52, 0x42, 0xc8, 0xe3, 0x3f, 0x3a, 0x07, 0xca, 0xa5,
+ 0xdb, 0x22, 0x83, 0x29, 0xe1, 0x99, 0xa1, 0x89, 0x16, 0x27,
+ 0xb2, 0x4e, 0x19, 0x66, 0xd8, 0xdd, 0x98, 0x70, 0xab, 0x83,
+ 0x11, 0x4e, 0xe8, 0x2d, 0x3d, 0x9c, 0x58, 0x8f, 0xd0, 0xeb,
+ 0xf7, 0x9a, 0xba, 0x1c, 0x16, 0xd0, 0xcd, 0xa8, 0xff, 0xdb,
+ 0x3f, 0xbc, 0x19, 0xae, 0x90, 0x1b, 0xce, 0x02, 0x1d, 0x75,
+ 0xd3, 0xd1, 0x6f, 0xd1, 0xc2, 0xf0, 0xeb, 0xab, 0xb1, 0xe2,
+ 0x47, 0xc1, 0x4b, 0x73, 0x46, 0x7b, 0xbc, 0x3b, 0xef, 0xfb,
+ 0xe0, 0x63, 0xcf, 0xaa, 0xcf, 0x4c, 0x06, 0x91, 0xd4, 0x00,
+ 0xbd, 0x87, 0x20, 0x0d, 0x4e, 0x2d, 0x09, 0x7c, 0x13, 0xb4,
+ 0xbb, 0x6f, 0x21, 0x6a, 0x30, 0xa8, 0x4d, 0x91, 0x46, 0xc0,
+ 0x4b, 0xfb, 0x6e, 0x74, 0xf6, 0x4d, 0xbb, 0x32, 0x5b, 0xf1,
+ 0x27, 0x4b, 0xa3, 0x6a, 0x1d, 0xf8, 0x1e, 0x9a, 0xc0, 0x6c,
+ 0x16, 0x04, 0x4b, 0xca, 0xb6, 0xd4, 0xae, 0x79, 0xa9, 0x6c,
+ 0x3c, 0x2e, 0xb2, 0xb4, 0xef, 0x73, 0xd2, 0xe9, 0xf4, 0x71,
+ 0x2f, 0x34, 0xe3, 0x7c, 0x1b, 0xf1, 0x52, 0x0d, 0xa8, 0x5f,
+ 0xac, 0x7c, 0x48, 0xf0, 0xb3, 0x37, 0x05, 0x4d, 0xbe, 0x69,
+ 0x27, 0xda, 0x04, 0xc2, 0x33, 0xa3, 0x45, 0xb1, 0x3d, 0x3f,
+ 0xe1, 0x12, 0x94, 0x2e, 0xd6, 0xf1, 0xfa, 0xaf, 0x1b, 0x91,
+ 0xe3, 0x3f, 0x59, 0x40, 0x25, 0xd0, 0xba, 0xda, 0xa0, 0x46,
+ 0x14, 0x59, 0x1d, 0x29, 0xd3, 0xdc, 0xfb, 0xbe, 0x25, 0xb5,
+ 0x1d, 0x9d, 0xd5, 0x93, 0x03, 0x8f, 0xb1, 0x72, 0x45, 0xf7,
+ 0x6b, 0x89, 0xbb, 0x04, 0xbf, 0x2c, 0x17, 0x7a, 0x2a, 0x6c,
+ 0xa1, 0x85, 0xac, 0x88, 0x90, 0x8a, 0xee, 0x7e, 0x25, 0x39,
+ 0x29, 0xe8, 0x6a, 0x8f, 0x68, 0x24, 0x97, 0xb0, 0x4d, 0x8e,
+ 0x9c, 0x9b, 0xb5, 0xd4, 0x0b, 0x6c, 0xd4, 0x14, 0xb5, 0xa1,
+ 0x47, 0x30, 0x1d, 0xd8, 0x84, 0xf6, 0xb5, 0xba, 0xb6, 0xf2,
+ 0x90, 0xce, 0xd6, 0x9c, 0x71, 0x71, 0xe0, 0xf3, 0xcc, 0x8b,
+ 0xa6, 0x18, 0x88, 0x6d, 0x1e, 0x2f, 0x3c, 0x6b, 0x69, 0x89,
+ 0xe1, 0x33, 0xc5, 0xfb, 0x4c, 0x53, 0x55, 0x55, 0x10, 0xb7,
+ 0xcf, 0x35, 0x84, 0xf6, 0x07, 0xd2, 0xae, 0xc2, 0xc4, 0xf5,
+ 0x9c, 0xd8, 0xeb, 0x7d, 0xf7, 0xdc, 0xb4, 0xbb, 0xf0, 0xbe,
+ 0xdd, 0x3d, 0x3f, 0xcb, 0xd2, 0xa1, 0x52, 0x9e, 0xc9, 0x5c,
+ 0x05, 0x03, 0xf9, 0x9c, 0xf6, 0xdf, 0xb3, 0xd4, 0xe2, 0x54,
+ 0x34, 0x62, 0x66, 0xf8, 0xa6, 0xed, 0x6d, 0xef, 0x9f, 0xa6,
+ 0x49, 0x61, 0x87, 0x2f, 0x54, 0x48, 0x99, 0xdd, 0xd5, 0xa3,
+ 0xe2, 0xc7, 0xbb, 0x69, 0x17, 0x3a, 0xdc, 0x6f, 0xbd, 0xb6,
+ 0x84, 0xf3, 0xa2, 0x8c, 0x3f, 0x16, 0x12, 0x50, 0x62, 0xa0,
+ 0xc8, 0x47, 0xd3, 0xa6, 0x9a, 0x67, 0xe6, 0xc5, 0xf9, 0x00,
+ 0xca, 0x32, 0x9f, 0x20, 0x87, 0xd7, 0xea, 0x15, 0x18, 0xbb,
+ 0xe0, 0x24, 0x4f, 0x5b, 0x02, 0xb4, 0x2e, 0xc9, 0x1c, 0x66,
+ 0x45, 0x54, 0x01, 0xe3, 0xe0, 0xd0, 0xc2, 0x5c, 0x8c, 0x69,
+ 0x1c, 0xa7, 0xf7, 0x15, 0x25, 0x7d, 0x76, 0xb4, 0xba, 0x8d,
+ 0xa0, 0x5f, 0x1e, 0x46, 0x8e, 0xe1, 0x40, 0xd2, 0xb7, 0x1f,
+ 0x2f, 0x78, 0xec, 0xb5, 0x81, 0xc4, 0xa7, 0xb1, 0x07, 0x4d,
+ 0x7f, 0xa1, 0x15, 0x85, 0x7b, 0xdb, 0xaa, 0x84, 0x80, 0xc6,
+ 0x5a, 0x32, 0xcb, 0xa3, 0x0f, 0x5f, 0xb9, 0x71, 0xae, 0xfa,
+ 0x03, 0x2a, 0xb5, 0xf8, 0x61, 0x96, 0x80, 0xcb, 0xdf, 0x69,
+ 0xc6, 0xb0, 0x05, 0x4d, 0x1a, 0x72, 0x6b, 0xb0, 0xed, 0x3e,
+ 0x52, 0x6f, 0x60, 0xf5, 0x64, 0x4e, 0x18, 0xbb, 0x45, 0x0c,
+ 0xe7, 0xc2, 0x1a, 0xbc, 0xcc, 0xff, 0x6d, 0x3f, 0x15, 0x57,
+ 0x94, 0x1c, 0xa2, 0x25, 0x3d, 0xb5, 0x46, 0xe6, 0xc1, 0x26,
+ 0x39, 0xe1, 0x3d, 0xa4, 0x44, 0x74, 0xf3, 0x0e, 0x92, 0x3c,
+ 0xd6, 0xa1, 0x93, 0xe0, 0xd6, 0x7b, 0x5d, 0xd3, 0x60, 0x60,
+ 0x6a, 0xa4, 0xa0, 0x20, 0x02, 0x6e, 0x89, 0x54, 0xf7, 0x00,
+ 0x8a, 0xc1, 0x3c, 0x77, 0xfe, 0x60, 0xb6, 0xa0, 0x12, 0x1e,
+ 0xd1, 0xfd, 0x9f, 0x3d, 0xc6, 0x16, 0x1f, 0x09, 0x01, 0x97,
+ 0x10, 0x3a, 0xa1, 0x59, 0xa4, 0xf8, 0xde, 0xe3, 0x15, 0xfe,
+ 0x95, 0x83, 0x0f, 0x9c, 0xc2, 0xcb, 0xe6, 0x91, 0x3d, 0x49,
+ 0x36, 0xc9, 0xfc, 0x02, 0x40, 0x1f, 0x3a, 0x91, 0xf9, 0x6e,
+ 0x08, 0xa7, 0xb3, 0x8b, 0x9d, 0x6c, 0x7e, 0x71, 0xab, 0xe6,
+ 0x61, 0xa2, 0xb1, 0xef, 0x31, 0xa8, 0xa9, 0x02, 0x1a, 0xa2,
+ 0x89, 0xf8, 0xec, 0xee, 0x97, 0xf6, 0xa3, 0xc0, 0xa7, 0x7f,
+ 0xca, 0xb9, 0x75, 0x52, 0x18, 0xf3, 0x8d, 0x02, 0xb8, 0x59,
+ 0x7a, 0x20, 0xf6, 0xf3, 0x14, 0x33, 0x68, 0x70, 0xfc, 0x02,
+ 0xe1, 0x74, 0x4f, 0x6e, 0xc9, 0x9c, 0x5d, 0xc7, 0xc0, 0xb3,
+ 0xe4, 0x5b, 0x68, 0x5d, 0x56, 0x41, 0x8d, 0x88, 0x40, 0x96,
+ 0x55, 0xfe, 0x71, 0x90, 0x50, 0x61, 0x34, 0xc1, 0x60, 0xa9,
+ 0xf0, 0x92, 0x07, 0x70, 0x72, 0x71, 0xfc, 0x53, 0x35, 0x9c,
+ 0x25, 0x35, 0xc9, 0x01, 0xde, 0xa0, 0x6e, 0x1e, 0x3e, 0x05,
+ 0x95, 0x57, 0xa3, 0xb5, 0xbe, 0xc0, 0xc5, 0x9e, 0x4c, 0xd0,
+ 0x09, 0x50, 0xfa, 0xd3, 0x1b, 0x6b, 0x90, 0xce, 0x85, 0x48,
+ 0x12, 0xbe, 0x9d, 0x95, 0xf3, 0xc7, 0x5d, 0xcb, 0x12, 0x08,
+ 0x2c, 0x49, 0xe1, 0xd5, 0x93, 0x67, 0x52, 0xa3, 0xac, 0x8f,
+ 0xdc, 0x8f, 0x64, 0xaa, 0x0c, 0x58, 0x52, 0x30, 0x4e, 0xef,
+ 0xf8, 0xfe, 0x2b, 0xbe, 0xd3, 0x6c, 0x7b, 0x30, 0xd6, 0xff,
+ 0x42, 0x94, 0xdc, 0xbf, 0x4e, 0x6b, 0x0d, 0xef, 0x5c, 0xd9,
+ 0x84, 0x43, 0x10, 0x22, 0x85, 0x24, 0xe9, 0x9a, 0x4b, 0xaf,
+ 0xb6, 0x9c, 0xce, 0x7b, 0x59, 0x0f, 0x3c, 0x51, 0x16, 0xc8,
+ 0x2b, 0x18, 0xd9, 0xce, 0x9f, 0x82, 0x86, 0xef, 0xc8, 0xaa,
+ 0x56, 0xfa, 0xbd, 0x0f, 0xd0, 0x8f, 0x66, 0x76, 0xed, 0x66,
+ 0x48, 0x8b, 0xa4, 0x67, 0xd1, 0xca, 0xd2, 0x62, 0x13, 0x7c,
+ 0xde, 0xdf, 0xfe, 0xfc, 0xf9, 0x1a, 0x16, 0x4a, 0xfc, 0xb2,
+ 0xc1, 0xdd, 0x92, 0x44, 0x9e, 0xe5, 0x44, 0x62, 0xee, 0xeb,
+ 0xbe, 0xa4, 0x9a, 0x95, 0x3d, 0xb3, 0x69, 0x3f, 0xda, 0x9a,
+ 0x24, 0xdc, 0x90, 0x56, 0xe0, 0x46, 0x3e, 0x90, 0xf5, 0xea,
+ 0x98, 0x57, 0x89, 0x9b, 0xf1, 0xd2, 0x67, 0xd7, 0x46, 0x43,
+ 0xf5, 0x7d, 0x7d, 0x2c, 0xe1, 0x6a, 0xd2, 0x79, 0x77, 0xef,
+ 0xd1, 0xa1, 0x0f, 0x8c, 0x1a, 0xd7, 0x19, 0x55, 0x86, 0xf2,
+ 0xc7, 0xa8, 0x9b, 0x32, 0xf9, 0x5e, 0x45, 0x23, 0x52, 0x2d,
+ 0xb6, 0xdc, 0x65, 0xbf, 0x98, 0xa4, 0x41, 0xff, 0xbb, 0x89,
+ 0x79, 0xec, 0x16, 0xa2, 0xf8, 0x05, 0x69, 0x64, 0xdf, 0x95,
+ 0x6b, 0x7d, 0xfc, 0xce, 0x3d, 0xa1, 0xd8, 0xbf, 0x83, 0xe2,
+ 0x89, 0xc9, 0x1a, 0x36, 0xb9, 0xff, 0xc3, 0x6f, 0x04, 0x1a,
+ 0xfc, 0x5c, 0x6c, 0x3f, 0x78, 0x7c, 0xf3, 0xe2, 0x0b, 0x72,
+ 0x55, 0x13, 0x8b, 0x97, 0x12, 0x58, 0xf4, 0x4a, 0x52, 0x36,
+ 0xc6, 0x42, 0xca, 0xf5, 0xf0, 0x31, 0x9d, 0xf3, 0x60, 0x56,
+ 0x26, 0xff, 0x81, 0xf8, 0xd3, 0x82, 0x76, 0x1d, 0xb8, 0x9e,
+ 0xc8, 0x53, 0xab, 0x54, 0x22, 0xf8, 0xef, 0x37, 0x8e, 0xc3,
+ 0x7b, 0xe9, 0xbf, 0x25, 0x2e, 0x56, 0x2a, 0x18, 0xa9, 0xec,
+ 0x39, 0x28, 0xa5, 0x9c, 0x8e, 0xc5, 0xaa, 0x7d, 0xa6, 0x97,
+ 0x8f, 0x44, 0xcf, 0x62, 0xb3, 0xa1, 0x6f, 0x26, 0xcf, 0x5c,
+ 0x3d, 0x5c, 0x47, 0x40, 0x8f, 0x5d, 0x70, 0x68, 0x01, 0x69,
+ 0x26, 0x5d, 0x45, 0xa0, 0x60, 0xb1, 0x49, 0xa9, 0xe3, 0x6c,
+ 0xe7, 0x2a, 0x64, 0x02, 0x9e, 0x43, 0x37, 0x0f, 0x57, 0xd3,
+ 0xa5, 0xeb, 0x11, 0xe6, 0x69, 0x55, 0xd1, 0x85, 0x6c, 0x78,
+ 0xc9, 0x8b, 0x44, 0xbc, 0x85, 0x80, 0x93, 0xba, 0x6c, 0xe3,
+ 0xd2, 0x39, 0x95, 0xc6, 0x49, 0xd1, 0xa9, 0x46, 0x50, 0xd6,
+ 0xb2, 0x7c, 0x12, 0x5c, 0xa7, 0x7f, 0x2c, 0xb3, 0xaa, 0x01,
+ 0x4a, 0x06, 0x58, 0xef, 0xe3, 0xad, 0x8c, 0xdc, 0xc6, 0x45,
+ 0xe6, 0x18, 0x33, 0xcb, 0x19, 0xba, 0x8f, 0x76, 0x72, 0x41,
+ 0xd5, 0xe3, 0xa9, 0x8f, 0x93, 0x50, 0xdc, 0x7a, 0xcd, 0x4e,
+ 0x94, 0xc2, 0xaa, 0xb4, 0xc0, 0xf4, 0xad, 0xec, 0x46, 0xff,
+ 0x65, 0x73, 0x54, 0xb5, 0xfc, 0xbb, 0xd2, 0x61, 0x14, 0xae,
+ 0xab, 0xf7, 0x52, 0x95, 0xcd, 0x1c, 0x3f, 0x25, 0xc9, 0xc9,
+ 0xe3, 0x19, 0x79, 0x62, 0xac, 0x02, 0x85, 0x8a, 0xc9, 0x3a,
+ 0xf1, 0xc5, 0xd2, 0xbd, 0xdc, 0x8f, 0xd7, 0xce, 0xfe, 0xa2,
+ 0x58, 0x14, 0xc5, 0xa2, 0xd9, 0xef, 0x2d, 0x9d, 0x97, 0xf7,
+ 0xec, 0x55, 0xf7, 0x2e, 0x4c, 0x0f, 0x2f, 0x3b, 0xde, 0xc0,
+ 0x41, 0x7f, 0x7d, 0x2a, 0xb9, 0x56, 0xab, 0xe6, 0xf4, 0x11,
+ 0x52, 0xb6, 0xe6, 0xc9, 0xe6, 0xc6, 0xef, 0x02, 0x6e, 0x4a,
+ 0xd1, 0x6d, 0x3c, 0xa6, 0x0c, 0xb8, 0xd6, 0x7e, 0x31, 0x3a,
+ 0xa0, 0xce, 0xcc, 0x0c, 0x31, 0xab, 0xc8, 0xe8, 0x37, 0xfd,
+ 0x0e, 0x5b, 0xc3, 0x62, 0xf8, 0xdb, 0xcb, 0xf5, 0x00, 0xa0,
+ 0x40, 0xb3, 0x48, 0xeb, 0x82, 0xae, 0xd2, 0xd3, 0x4a, 0xea,
+ 0x12, 0x6f, 0x39, 0x85, 0xbb, 0x98, 0xb7, 0x39, 0x3f, 0xf3,
+ 0x0b, 0x0e, 0x6d, 0xca, 0x84, 0xf8, 0xf1, 0x35, 0xdc, 0xe7,
+ 0xbd, 0x1c, 0xdd, 0x21, 0x4b, 0xdf, 0xde, 0xb5, 0xcc, 0x16,
+ 0x87, 0x0f, 0xe8, 0xb8, 0x34, 0x86, 0x45, 0xfc, 0xf4, 0x61,
+ 0x93, 0x4a, 0x7f, 0x2c, 0x3f, 0xeb, 0xd1, 0xce, 0xc8, 0x61,
+ 0x0b, 0x72, 0x49, 0x3a, 0xd3, 0x3f, 0x06, 0x05, 0x4c, 0xe0,
+ 0x23, 0x4d, 0x8a, 0x20, 0xae, 0x87, 0xda, 0x4c, 0x6c, 0xb8,
+ 0xb9, 0x79, 0xda, 0x1c, 0x73, 0x5e, 0xb6, 0xf2, 0x03, 0x61,
+ 0x68, 0xff, 0xd5, 0xbf, 0x9a, 0x04, 0xb4, 0x73, 0xc8, 0xda,
+ 0xb5, 0x82, 0x34, 0x0c, 0x95, 0xfb, 0xda, 0x70, 0xcb, 0xb2,
+ 0xde, 0xa8, 0x08, 0xdf, 0xe4, 0x24, 0xff, 0x77, 0x5c, 0x2f,
+ 0x18, 0x0c, 0x82, 0x1f, 0xe6, 0x29, 0x09, 0x8f, 0xfe, 0x67,
+ 0x96, 0xb2, 0x47, 0x9c, 0x43, 0x3a, 0x58, 0xe8, 0x5f, 0xc9,
+ 0x73, 0xa4, 0x65, 0x3c, 0xe3, 0x7f, 0xdc, 0xde, 0x0d, 0xdf,
+ 0x1c, 0x4d, 0x0e, 0x3e, 0xc3, 0xd2, 0x1c, 0xc1, 0x0e, 0x4a,
+ 0x44, 0xf7, 0x74, 0xce, 0x00, 0x69, 0x43, 0x99, 0xbd, 0xb7,
+ 0xa1, 0x36, 0xff, 0x0a, 0xc8, 0x6b, 0xdd, 0xd8, 0x62, 0x2f,
+ 0xf5, 0x37, 0x95, 0x8d, 0x91, 0xd8, 0xa0, 0x5b, 0xc9, 0xa7,
+ 0xfe, 0x6c, 0xc0, 0x45, 0x7c, 0x00, 0x0d, 0x4a, 0xbf, 0x3e,
+ 0x00, 0x50, 0xe6, 0xce, 0x69, 0x26, 0xa6, 0x1d, 0xa4, 0xb0,
+ 0x4b, 0x4e, 0x02, 0x5b, 0x01, 0x0a, 0x1c, 0xcb, 0x11, 0x6e,
+ 0x74, 0xfe, 0xcc, 0xe1, 0x62, 0xd7, 0xf1, 0x56, 0xf9, 0x3b,
+ 0x69, 0x46, 0x8c, 0x1c, 0x52, 0x37, 0x25, 0x33, 0x35, 0x03,
+ 0xea, 0x51, 0x7a, 0x29, 0x85, 0x9a, 0x58, 0x4f, 0x38, 0x9f,
+ 0xc6, 0x57, 0x54, 0xba, 0x8e, 0x92, 0x59, 0xcc, 0x4f, 0x88,
+ 0xd5, 0x4a, 0x33, 0x82, 0x9f, 0x2c, 0x7d, 0x2b, 0x99, 0xce,
+ 0x22, 0x3b, 0x2d, 0xc9, 0x6f, 0x4d, 0x38, 0x87, 0x45, 0x20,
+ 0xc5, 0x67, 0x41, 0xf2, 0xce, 0xd9, 0x3b, 0x77, 0x41, 0xad,
+ 0x35, 0x93, 0x0a, 0x53, 0x1a, 0xb2, 0x15, 0xc9, 0x91, 0xb9,
+ 0xc7, 0x68, 0x5c, 0x1b, 0x29, 0xd2, 0x99, 0xb5, 0x38, 0xa2,
+ 0x60, 0x5d, 0xc2, 0x2c, 0x36, 0xef, 0xf7, 0xed, 0xfa, 0xe2,
+ 0x33, 0xdb, 0xb4, 0x35, 0x23, 0xd4, 0x18, 0x90, 0x4e, 0xe3,
+ 0xb4, 0xec, 0xcd, 0x42, 0xbf, 0x35, 0x28, 0x24, 0x88, 0x18,
+ 0x3d, 0xfc, 0x97, 0xa6, 0x12, 0x96, 0x68, 0x5b, 0xd4, 0x5b,
+ 0xf0, 0xe9, 0xda, 0xc7, 0xbd, 0xe1, 0xca, 0x3f, 0x86, 0xff,
+ 0x8e, 0x16, 0xaa, 0x28, 0x5a, 0x9d, 0xbd, 0x6a, 0xd3, 0xa7,
+ 0x7b, 0xf8, 0x32, 0x39, 0x87, 0xa2, 0x21, 0x03, 0x11, 0x13,
+ 0xe6, 0xe7, 0x86, 0x68, 0x92, 0x93, 0x0a, 0x71, 0xa5, 0x5b,
+ 0x95, 0x01, 0xae, 0x09, 0xb5, 0x68, 0xee, 0x0d, 0xd7, 0x2f,
+ 0x24, 0x7a, 0x44, 0xf4, 0x00, 0x58, 0x23, 0x07, 0xd3, 0x25,
+ 0x7c, 0x1a, 0x48, 0x4a, 0x20, 0x06, 0x1d, 0x1f, 0xc4, 0xf8,
+ 0x5b, 0xa1, 0x88, 0xb3, 0x53, 0x2b, 0x65, 0x89, 0xd4, 0x26,
+ 0x0b, 0xc1, 0xc4, 0x0f, 0xc5, 0x2f, 0xab, 0xf6, 0xc8, 0xc6,
+ 0x98, 0x73, 0x81, 0x91, 0x87, 0x7c, 0x9f, 0x50, 0x66, 0x50,
+ 0xc3, 0xcc, 0xff, 0x7a, 0x9c, 0x25, 0x14, 0x73, 0x94, 0x4b,
+ 0x8a, 0x51, 0x98, 0x23, 0x52, 0x96, 0x20, 0xa8, 0xe9, 0x0f,
+ 0x38, 0x36, 0xc4, 0x51, 0xec, 0xea, 0xe4, 0xea, 0xe9, 0x8e,
+ 0x44, 0x47, 0x13, 0x75, 0xea, 0xb1, 0x90, 0x0a, 0x66, 0xd1,
+ 0x17, 0xd5, 0xd7, 0x2f, 0x9e, 0x56, 0x86, 0xd1, 0x98, 0x9c,
+ 0x24, 0xa2, 0x82, 0x84, 0xad, 0xee, 0x50, 0xd8, 0xc1, 0x52,
+ 0xf1, 0xa7, 0xa2, 0x96, 0xfe, 0x34, 0x6f, 0xf2, 0xf7, 0x37,
+ 0x53, 0x7f, 0xa5, 0x4f, 0x4a, 0x5e, 0xe5, 0x65, 0x2c, 0xb1,
+ 0xc5, 0x22, 0x7b, 0xbe, 0xb0, 0x99, 0x61, 0xc8, 0xf3, 0x54,
+ 0x20, 0x9c, 0x6e, 0x45, 0x1a, 0xdf, 0x68, 0x5c, 0x80, 0x32,
+ 0xad, 0xd3, 0xa4, 0xac, 0x28, 0xeb, 0x02, 0xc8, 0x31, 0xc0,
+ 0x74, 0x5b, 0x9b, 0xe0, 0x20, 0x72, 0x12, 0x93, 0xc2, 0x93,
+ 0x2b, 0x77, 0xcb, 0x2c, 0x08, 0xaa, 0x48, 0x07, 0x05, 0x77,
+ 0x27, 0x6d, 0x43, 0x1b, 0x75, 0xc5, 0xda, 0x1d, 0x6b, 0x41,
+ 0x6b, 0x56, 0x5c, 0xf3, 0x0e, 0x8b, 0x17, 0xa2, 0xe8, 0x15,
+ 0x63, 0xbd, 0x07, 0xfa, 0xcf, 0xea, 0x1b, 0xe7, 0x6d, 0xc1,
+ 0xf3, 0x8a, 0x0b, 0x21, 0x4e, 0x66, 0x47, 0xa4, 0xd4, 0xed,
+ 0x3b, 0xfa, 0x7c, 0x65, 0xac, 0x4d, 0x38, 0xda, 0x4f, 0x2a,
+ 0xc9, 0x52, 0xb1, 0x6e, 0x1f, 0xae, 0xba, 0xde, 0x8c, 0x1d,
+ 0x73, 0x49, 0xdc, 0x7b, 0x04, 0xdf, 0xda, 0xdc, 0x24, 0xd6,
+ 0xe1, 0x0a, 0x1a, 0x30, 0x85, 0x00, 0xea, 0xe1, 0xc8, 0xa9,
+ 0x98, 0x84, 0xdd, 0xa6, 0x8b, 0xe6, 0xbf, 0x96, 0x2b, 0x9f,
+ 0x27, 0xce, 0x71, 0x4d, 0x4b, 0xdc, 0x21, 0xcb, 0x2f, 0x17,
+ 0xa8, 0xa5, 0x98, 0xa3, 0x7a, 0xb1, 0x24, 0x79, 0x92, 0x1d,
+ 0xbd, 0x8d, 0x22, 0x8d, 0xb8, 0x1a, 0x5e, 0x0c, 0x3f, 0x00,
+ 0xe7, 0xe6, 0x9b, 0x43, 0xcc, 0xe1, 0x9f, 0x1c, 0xde, 0x17,
+ 0x59, 0xfd, 0xf4, 0xfc
+ };
+
+ static const uint8_t keys_b_ps4[0x70 * 0x20] = {
+ 0x76, 0x65, 0x9d, 0xe4, 0xf7, 0x6c, 0x45, 0x1f, 0x73, 0x42,
+ 0x47, 0xc3, 0x92, 0x32, 0xf4, 0x38, 0x6f, 0x28, 0xcf, 0xfa,
+ 0x41, 0x84, 0xbe, 0x1b, 0x5a, 0x23, 0x99, 0xfa, 0xcd, 0x74,
+ 0xf0, 0x3b, 0x43, 0xc9, 0xd1, 0x30, 0x66, 0x77, 0x5e, 0xac,
+ 0x39, 0x8e, 0x8a, 0x6c, 0x5c, 0xfe, 0xdd, 0x80, 0xf3, 0x03,
+ 0x04, 0x30, 0x84, 0x9d, 0x12, 0xee, 0x4a, 0xe3, 0x0f, 0x5e,
+ 0x5e, 0x97, 0xd0, 0xf9, 0x1b, 0x57, 0x05, 0x74, 0x03, 0x5a,
+ 0x5d, 0x41, 0xda, 0x6e, 0xfd, 0x4b, 0x1e, 0xc4, 0x4b, 0x76,
+ 0xe4, 0xd3, 0x39, 0xeb, 0x3c, 0x2c, 0xae, 0x27, 0xe2, 0x98,
+ 0xe4, 0x08, 0xa3, 0xbe, 0xb0, 0x31, 0x69, 0x14, 0xa3, 0x6c,
+ 0xdf, 0xfc, 0x1f, 0x56, 0x0d, 0xfe, 0x55, 0x12, 0x97, 0x5e,
+ 0xad, 0x3d, 0x9b, 0xee, 0xb5, 0xad, 0xa3, 0x56, 0x85, 0xc3,
+ 0xbd, 0x9b, 0xef, 0xac, 0x55, 0x98, 0x1f, 0xaf, 0xb8, 0x0b,
+ 0xae, 0xed, 0xf3, 0x87, 0x34, 0xc9, 0x5b, 0x69, 0xf0, 0xed,
+ 0x4f, 0x03, 0x40, 0xa5, 0x71, 0x24, 0xa2, 0x23, 0x93, 0x52,
+ 0xe9, 0x87, 0xc4, 0x79, 0x4f, 0xe9, 0x20, 0xbc, 0x04, 0x58,
+ 0x70, 0x96, 0xb1, 0xd7, 0x37, 0xc4, 0x29, 0x4d, 0x3d, 0x52,
+ 0xd3, 0xcf, 0x7c, 0x08, 0xe9, 0x82, 0xc6, 0x99, 0xf5, 0xdb,
+ 0x60, 0x11, 0x9b, 0x9d, 0x25, 0x86, 0x08, 0xb3, 0x77, 0x27,
+ 0xa8, 0x2c, 0xf9, 0x28, 0x2f, 0x2b, 0x70, 0x6c, 0x4b, 0xbb,
+ 0x1e, 0x49, 0x3e, 0xd6, 0x69, 0xd8, 0xe8, 0x09, 0xf4, 0x9c,
+ 0x09, 0x32, 0xdf, 0xdd, 0xe1, 0x0b, 0x41, 0x0e, 0x26, 0x3e,
+ 0x53, 0xcc, 0x7a, 0xd1, 0x15, 0x16, 0xfb, 0xc3, 0x09, 0xd3,
+ 0xad, 0xa4, 0xea, 0xa3, 0x4a, 0xbe, 0xf2, 0x88, 0x9a, 0x34,
+ 0x7b, 0x09, 0x41, 0x44, 0xf1, 0x5e, 0x3c, 0x4f, 0x0e, 0x9f,
+ 0x5a, 0x95, 0x57, 0x7a, 0xfe, 0xbe, 0xc8, 0x24, 0x1f, 0x98,
+ 0x85, 0x1e, 0x25, 0x74, 0x90, 0xdd, 0x5a, 0xd1, 0x7c, 0x02,
+ 0xbe, 0x63, 0xe5, 0xc7, 0xa2, 0x0b, 0x26, 0xdf, 0x20, 0x84,
+ 0x61, 0xe3, 0xff, 0x2c, 0x69, 0x32, 0x91, 0x62, 0xf8, 0x11,
+ 0x7c, 0xf2, 0x1e, 0xe5, 0x1e, 0xc1, 0xb2, 0x2a, 0x84, 0x8e,
+ 0x3a, 0x78, 0xf6, 0xf6, 0x76, 0x76, 0x4e, 0x34, 0x4f, 0x24,
+ 0xf0, 0x8c, 0x34, 0xaf, 0x95, 0x7a, 0x35, 0x26, 0x5e, 0x28,
+ 0xe3, 0x2c, 0x66, 0xf9, 0x7f, 0x35, 0xfc, 0xad, 0xfe, 0x6e,
+ 0x4b, 0xe1, 0x5c, 0x48, 0xec, 0x1a, 0xba, 0xbd, 0xac, 0x73,
+ 0x7a, 0xe8, 0xbc, 0xad, 0xba, 0x5e, 0xa6, 0xf0, 0xb8, 0x5a,
+ 0x51, 0x2a, 0x97, 0xe4, 0x34, 0x8a, 0x3b, 0x64, 0x61, 0x97,
+ 0x57, 0x59, 0x29, 0x87, 0x76, 0x80, 0x78, 0x05, 0x91, 0xc0,
+ 0xa5, 0x5e, 0x7b, 0x5a, 0xa5, 0x7b, 0x2f, 0x8d, 0x07, 0x7d,
+ 0xfa, 0x75, 0x32, 0xe8, 0x48, 0x0c, 0x68, 0xbf, 0x70, 0xb3,
+ 0x79, 0x88, 0xc0, 0xbd, 0xbc, 0x49, 0x9a, 0x9a, 0x07, 0xb8,
+ 0xa3, 0xc2, 0xb4, 0x1d, 0xca, 0x24, 0x90, 0xe8, 0x2f, 0xe8,
+ 0x9f, 0x28, 0xba, 0x40, 0x17, 0x50, 0x56, 0xcf, 0x4e, 0xc8,
+ 0x82, 0xcb, 0x77, 0xf1, 0x74, 0x8d, 0xef, 0x0b, 0x4c, 0x1f,
+ 0x3d, 0x4d, 0xd2, 0x4e, 0x66, 0x2f, 0x13, 0xe7, 0x8a, 0xb6,
+ 0xea, 0xd1, 0xd7, 0x36, 0xf3, 0x51, 0x65, 0x07, 0xc1, 0x88,
+ 0x7d, 0xae, 0xf4, 0xb8, 0x89, 0xb5, 0xce, 0xdd, 0x27, 0xbe,
+ 0x6f, 0x6c, 0x81, 0x8f, 0x56, 0x02, 0x0d, 0x49, 0xe4, 0x3f,
+ 0x60, 0x93, 0x55, 0xa3, 0x68, 0x75, 0x5d, 0xc5, 0x5a, 0x09,
+ 0x90, 0xdc, 0x8f, 0xaa, 0x9f, 0x47, 0xf6, 0xa9, 0xad, 0xdb,
+ 0x5b, 0xe2, 0x64, 0xce, 0xc1, 0x26, 0xeb, 0xeb, 0xa0, 0x03,
+ 0x23, 0x61, 0x14, 0x9e, 0x72, 0x48, 0xc2, 0x10, 0x3d, 0x73,
+ 0xc3, 0xc9, 0x22, 0x01, 0x68, 0x2c, 0x3c, 0x98, 0xa5, 0x85,
+ 0x0d, 0xa4, 0xec, 0xc6, 0xfc, 0x7d, 0xff, 0x2c, 0xae, 0x60,
+ 0xc4, 0xb0, 0x71, 0xb9, 0xbc, 0xdb, 0x29, 0xc6, 0x78, 0x0b,
+ 0xf6, 0x82, 0x38, 0xed, 0x35, 0xb7, 0x92, 0x2f, 0xeb, 0xa9,
+ 0xe7, 0xc3, 0x1a, 0xa2, 0xae, 0x44, 0x51, 0x71, 0x6e, 0x8c,
+ 0x21, 0x9f, 0x9c, 0x5d, 0x98, 0x72, 0xa3, 0x2e, 0x74, 0x49,
+ 0x70, 0xaf, 0x85, 0x7b, 0x14, 0x66, 0x56, 0x99, 0x89, 0x07,
+ 0xc4, 0xa9, 0xc2, 0xc2, 0xb3, 0x39, 0xa6, 0xf5, 0x5b, 0x3f,
+ 0x30, 0xcc, 0x2d, 0x03, 0x22, 0xb4, 0x2a, 0x27, 0x1a, 0x46,
+ 0xbc, 0xe5, 0xf6, 0x19, 0x34, 0xc1, 0x06, 0x6f, 0x28, 0x84,
+ 0xe1, 0xe3, 0xb0, 0x21, 0x21, 0x84, 0x34, 0x33, 0x8d, 0x01,
+ 0x26, 0x44, 0x5f, 0xa3, 0x56, 0x19, 0x46, 0xc1, 0x78, 0x29,
+ 0x38, 0x0b, 0x9f, 0xe1, 0xf1, 0x2b, 0xfc, 0x01, 0x91, 0x71,
+ 0x1c, 0xa5, 0x9c, 0xb6, 0xe1, 0x5e, 0xfa, 0x26, 0x56, 0x3f,
+ 0xa8, 0xb2, 0xd1, 0xe4, 0xf3, 0x69, 0x08, 0x36, 0xac, 0xd6,
+ 0x67, 0xb6, 0xe6, 0xb8, 0xc7, 0xa6, 0x7a, 0x10, 0x49, 0xe7,
+ 0xba, 0x6f, 0xbf, 0xe8, 0x25, 0xaf, 0xc2, 0xdf, 0xf3, 0xa1,
+ 0x76, 0xf4, 0x2b, 0x8c, 0x6f, 0x01, 0xce, 0x96, 0x30, 0xd2,
+ 0x2d, 0xf8, 0x6c, 0xad, 0xed, 0x1e, 0xe5, 0x7a, 0xdf, 0x37,
+ 0xe6, 0xff, 0xed, 0x9d, 0x5f, 0xa9, 0xfb, 0xf1, 0xbc, 0xc7,
+ 0x47, 0x5a, 0x6f, 0x77, 0x83, 0x18, 0x0e, 0xba, 0x18, 0x2b,
+ 0x38, 0xb4, 0x36, 0x32, 0x36, 0xdc, 0x98, 0xec, 0x16, 0xa1,
+ 0x68, 0x42, 0x95, 0xee, 0xad, 0x43, 0x4c, 0x3d, 0x31, 0xcf,
+ 0xdb, 0x57, 0xae, 0x0f, 0x78, 0x52, 0xd8, 0xa6, 0x47, 0x4b,
+ 0xba, 0x94, 0xc2, 0xd2, 0x97, 0x56, 0x2b, 0xb0, 0xbd, 0x43,
+ 0x30, 0x05, 0x4b, 0x30, 0x7b, 0x8b, 0xe4, 0xee, 0x61, 0xc4,
+ 0xcd, 0xcb, 0x08, 0xb1, 0x16, 0x2e, 0xa3, 0xe4, 0x20, 0x0e,
+ 0xd5, 0x02, 0x17, 0x81, 0xba, 0x43, 0xf7, 0x02, 0x7a, 0x0e,
+ 0x77, 0x15, 0x45, 0x03, 0xaa, 0x84, 0x88, 0x56, 0xb1, 0xee,
+ 0x3a, 0x1f, 0xe6, 0x28, 0xfd, 0x9f, 0x05, 0x81, 0x8d, 0xbf,
+ 0x53, 0xa1, 0x5b, 0xbd, 0x9c, 0x79, 0x53, 0x28, 0x73, 0xba,
+ 0x0d, 0x72, 0xe7, 0x14, 0xed, 0x5d, 0xb7, 0x95, 0x09, 0xa0,
+ 0xbf, 0xef, 0x75, 0xa1, 0x20, 0xa4, 0x2d, 0x16, 0xa6, 0x58,
+ 0x61, 0x93, 0x3a, 0x62, 0x40, 0xca, 0x4e, 0xef, 0xe4, 0x31,
+ 0x18, 0xd1, 0x39, 0x35, 0x7c, 0x78, 0xe3, 0x6c, 0x76, 0x8a,
+ 0x37, 0xdd, 0xc3, 0x8e, 0x60, 0xe2, 0x0d, 0x67, 0xfc, 0xe7,
+ 0x37, 0x88, 0x48, 0xb6, 0xe4, 0x4b, 0x87, 0xe6, 0x04, 0x8c,
+ 0x06, 0xd8, 0xc7, 0x4e, 0x9a, 0x97, 0x9d, 0x9d, 0xa1, 0xfa,
+ 0x11, 0x27, 0x8c, 0x34, 0xe7, 0x02, 0x55, 0x0f, 0x6e, 0xc6,
+ 0xcd, 0x6b, 0xc4, 0xf0, 0xa1, 0x05, 0x6b, 0x3f, 0xc4, 0xc9,
+ 0x47, 0x95, 0x85, 0x1f, 0x80, 0x6a, 0x23, 0xe7, 0x24, 0x9f,
+ 0xc3, 0xd0, 0x9d, 0x17, 0xea, 0x2f, 0xfa, 0xfa, 0xe4, 0xa6,
+ 0x88, 0x9a, 0x57, 0x0c, 0x5b, 0xfe, 0xb4, 0x67, 0x3e, 0xf0,
+ 0x0a, 0x58, 0xa0, 0xc8, 0xc7, 0x16, 0xd6, 0x0b, 0x41, 0x9e,
+ 0xa9, 0xde, 0xbf, 0x17, 0x49, 0x08, 0x82, 0xc4, 0x4e, 0x56,
+ 0x53, 0xb0, 0x4e, 0x23, 0x1d, 0xa8, 0x07, 0xde, 0x98, 0xf8,
+ 0xe9, 0xec, 0x45, 0x3b, 0x50, 0xf6, 0xb6, 0xa3, 0xdc, 0xfa,
+ 0xa1, 0x86, 0xd3, 0xfa, 0xda, 0xb6, 0x1d, 0x27, 0xed, 0x3d,
+ 0x17, 0xc9, 0xcc, 0xf6, 0x2f, 0x7d, 0xea, 0x3b, 0x3d, 0x58,
+ 0xb7, 0xf0, 0x4a, 0xae, 0xf9, 0x2f, 0xa0, 0x80, 0xe9, 0xde,
+ 0x2e, 0x30, 0x3c, 0xd6, 0x46, 0xca, 0x50, 0x25, 0xf7, 0xa2,
+ 0x66, 0xec, 0xa5, 0x6e, 0x06, 0x6e, 0x00, 0x3e, 0x08, 0x6c,
+ 0xe6, 0x3b, 0xfb, 0xc8, 0xa7, 0xd0, 0x1e, 0xc8, 0x25, 0x24,
+ 0x11, 0x0b, 0x39, 0x0f, 0x70, 0xd9, 0xf8, 0xff, 0x54, 0x54,
+ 0x9d, 0x02, 0xab, 0x4c, 0x8c, 0x90, 0x94, 0xe5, 0x0e, 0xd5,
+ 0x62, 0x78, 0xab, 0xa5, 0x9c, 0x11, 0x1c, 0x70, 0x07, 0x9b,
+ 0x2a, 0xe4, 0xfb, 0xed, 0xfb, 0xcd, 0x92, 0x0d, 0xbc, 0x21,
+ 0x25, 0x33, 0x0b, 0xbf, 0xf3, 0x18, 0x93, 0x73, 0xed, 0xf3,
+ 0x88, 0x3b, 0x43, 0x97, 0xce, 0x19, 0x25, 0xf5, 0xbb, 0x71,
+ 0x26, 0x98, 0x2c, 0xd6, 0xff, 0x32, 0x4f, 0x75, 0xcc, 0x92,
+ 0x4e, 0xe3, 0xbe, 0xb8, 0xbf, 0x33, 0x89, 0xe5, 0x09, 0x4c,
+ 0x41, 0x8c, 0xb0, 0x51, 0x74, 0x32, 0xea, 0x96, 0xce, 0x70,
+ 0x1c, 0x5c, 0x26, 0x34, 0x66, 0x3a, 0x3a, 0xe8, 0x92, 0x4e,
+ 0xd2, 0x61, 0x70, 0x12, 0x3c, 0x33, 0x9d, 0x09, 0x57, 0x23,
+ 0x48, 0x81, 0x29, 0x7f, 0xc9, 0x4d, 0x06, 0xe8, 0xc5, 0x6e,
+ 0x50, 0x7c, 0x8f, 0xfa, 0xf0, 0xb4, 0xe6, 0x9c, 0x7d, 0x5e,
+ 0x09, 0x36, 0xb3, 0x24, 0xc4, 0xf8, 0xe3, 0x19, 0xa8, 0x65,
+ 0x3f, 0x2a, 0x84, 0x98, 0x79, 0x18, 0x58, 0x26, 0x29, 0x5e,
+ 0xbc, 0x3c, 0x7e, 0x80, 0xaa, 0x04, 0x43, 0x1b, 0xbd, 0x36,
+ 0xef, 0x5c, 0xc6, 0x78, 0xfb, 0xa6, 0xfd, 0x26, 0x6a, 0xa4,
+ 0xb6, 0x2e, 0x92, 0xb7, 0x7a, 0x02, 0xa7, 0xc8, 0xb0, 0x47,
+ 0xa8, 0x6e, 0x6a, 0x53, 0x15, 0x81, 0x7d, 0x1c, 0xb0, 0x55,
+ 0xbe, 0xa9, 0x11, 0x1e, 0xd0, 0xd8, 0x99, 0x37, 0xae, 0x35,
+ 0xfa, 0xe4, 0x0b, 0x4c, 0x52, 0x0c, 0x12, 0x3a, 0x5f, 0x09,
+ 0x4e, 0x21, 0xc6, 0x0d, 0x56, 0xe4, 0x23, 0xd2, 0x5e, 0x86,
+ 0x7f, 0x04, 0xb0, 0x1c, 0xeb, 0x9f, 0x50, 0x17, 0xcc, 0x8e,
+ 0xf9, 0x8f, 0x00, 0x91, 0xee, 0xa7, 0xa4, 0x91, 0x59, 0xb7,
+ 0x11, 0x9e, 0x87, 0x40, 0xe2, 0x41, 0x08, 0x0f, 0xec, 0x40,
+ 0xbc, 0x8c, 0x30, 0x14, 0xcc, 0x97, 0xc9, 0xf0, 0x50, 0x7b,
+ 0x5c, 0x18, 0xe2, 0x1b, 0x02, 0x7f, 0xda, 0xc0, 0xe6, 0x23,
+ 0x4b, 0x48, 0xbb, 0xe8, 0x01, 0xe9, 0xd4, 0xb8, 0x52, 0xfa,
+ 0x36, 0x99, 0xdd, 0x03, 0x8d, 0x21, 0xd3, 0x16, 0x17, 0x2c,
+ 0x25, 0x20, 0x70, 0x92, 0xd5, 0x57, 0xcb, 0xff, 0x70, 0x16,
+ 0x33, 0x18, 0xf3, 0x31, 0x1a, 0xb0, 0x07, 0x27, 0x82, 0xc5,
+ 0x13, 0x86, 0x3d, 0xb5, 0x22, 0xfb, 0x7d, 0x2a, 0x4d, 0x45,
+ 0xaa, 0x2f, 0x46, 0x67, 0x1e, 0x9f, 0xc4, 0x8b, 0x3a, 0xb8,
+ 0x8c, 0x56, 0x3c, 0xa4, 0xfe, 0xe7, 0x93, 0xb2, 0xed, 0xc8,
+ 0x19, 0x3c, 0xc0, 0x5d, 0x38, 0xb0, 0xa2, 0x54, 0xe8, 0x71,
+ 0xd1, 0x69, 0xc7, 0xb8, 0xdc, 0x33, 0x0c, 0x3b, 0x8f, 0xf4,
+ 0x37, 0x23, 0x3d, 0x1e, 0x7b, 0x50, 0xc5, 0x4c, 0x3a, 0x6a,
+ 0x8d, 0xa8, 0x2b, 0x3d, 0x8c, 0x63, 0x4e, 0x52, 0x79, 0xea,
+ 0x7f, 0xa2, 0x0c, 0xc0, 0xdc, 0xba, 0x60, 0x04, 0x86, 0xa5,
+ 0xd6, 0x2b, 0xab, 0x2a, 0x17, 0xbe, 0x95, 0x11, 0x2d, 0x14,
+ 0x4e, 0xc1, 0x42, 0xa3, 0x8e, 0xaf, 0x57, 0x4d, 0x84, 0xd9,
+ 0x3c, 0x91, 0xb9, 0x45, 0x09, 0x6a, 0xf5, 0xb3, 0x24, 0x4a,
+ 0x2d, 0x69, 0x46, 0x51, 0x65, 0x9a, 0x37, 0x11, 0x7d, 0xde,
+ 0x9c, 0x7c, 0x11, 0xaf, 0x81, 0xfa, 0xdf, 0x1e, 0x5d, 0xff,
+ 0xb2, 0x64, 0x33, 0x75, 0x23, 0x7f, 0x99, 0xc2, 0xa6, 0xb8,
+ 0x30, 0xdf, 0xd2, 0xb6, 0x5a, 0x35, 0x6b, 0xc5, 0x28, 0xb0,
+ 0x60, 0x1a, 0x85, 0x59, 0x2e, 0x2b, 0x8b, 0x8f, 0x9a, 0x4c,
+ 0xd2, 0x9b, 0x90, 0x1e, 0x95, 0x5a, 0xf3, 0xac, 0x34, 0x78,
+ 0x8c, 0xeb, 0x67, 0x09, 0x6b, 0x95, 0x73, 0x5e, 0x54, 0x1f,
+ 0x05, 0xd6, 0xe4, 0xf4, 0x3b, 0x4d, 0x93, 0x13, 0x8a, 0xa2,
+ 0x36, 0x62, 0xb9, 0x76, 0xdc, 0x77, 0xf7, 0x10, 0xf6, 0x73,
+ 0xb7, 0x49, 0x90, 0x53, 0x9a, 0xbe, 0x96, 0x9d, 0x4c, 0xd4,
+ 0xf5, 0x09, 0x15, 0x88, 0xe8, 0xc7, 0xbd, 0x04, 0x1e, 0xf8,
+ 0x70, 0x88, 0x7d, 0xf9, 0x2c, 0xf4, 0x80, 0x5c, 0x6f, 0x55,
+ 0x73, 0xf2, 0xa0, 0x90, 0xb4, 0xb4, 0xb9, 0x80, 0x49, 0x3a,
+ 0xc5, 0xda, 0x84, 0x8c, 0x26, 0x84, 0x90, 0xc1, 0x2b, 0xa6,
+ 0xac, 0x48, 0x2d, 0x37, 0x61, 0x27, 0xd2, 0x18, 0xb8, 0x51,
+ 0x28, 0x0b, 0x13, 0xbb, 0x35, 0x58, 0xe6, 0xf0, 0x24, 0x18,
+ 0x28, 0x42, 0x6d, 0xdd, 0xba, 0xe8, 0x64, 0xf2, 0xff, 0x47,
+ 0x27, 0x59, 0x16, 0x55, 0xcc, 0x5b, 0x54, 0xa3, 0xe2, 0xfb,
+ 0xef, 0x14, 0x68, 0x8d, 0xbf, 0x2e, 0x9a, 0xbc, 0xfa, 0xb8,
+ 0x8e, 0x5e, 0x37, 0x8a, 0x1b, 0x67, 0x09, 0xe8, 0x0b, 0xed,
+ 0x44, 0x71, 0x16, 0x05, 0xbc, 0x5b, 0x2a, 0xf8, 0x8a, 0xdd,
+ 0xd9, 0x74, 0x53, 0x73, 0xb0, 0xd9, 0xb9, 0xfd, 0xd6, 0x67,
+ 0x52, 0x7e, 0x2c, 0x50, 0x36, 0xd0, 0x3b, 0xf7, 0x1f, 0xd8,
+ 0x4a, 0x06, 0x11, 0xf3, 0x21, 0x1f, 0x6e, 0x8f, 0x24, 0x44,
+ 0xb8, 0xa1, 0xba, 0x0a, 0xbb, 0x6c, 0x18, 0x02, 0x8e, 0xe7,
+ 0x5b, 0xed, 0xaa, 0x39, 0xbb, 0xb5, 0x99, 0x00, 0xd9, 0x62,
+ 0x26, 0x5f, 0x26, 0x3e, 0x8e, 0x49, 0x11, 0xd4, 0x2e, 0x70,
+ 0x5e, 0x79, 0x08, 0x63, 0xfb, 0x8d, 0x70, 0x8f, 0x4f, 0x1d,
+ 0x4f, 0x33, 0x72, 0xcd, 0xb1, 0xbd, 0x20, 0x4c, 0x74, 0x7f,
+ 0xde, 0x54, 0xe3, 0x1e, 0xb1, 0x0d, 0xb1, 0xeb, 0x5c, 0x63,
+ 0xd6, 0xc9, 0xaf, 0x82, 0xa6, 0xc5, 0x19, 0xe7, 0x96, 0x2f,
+ 0xaf, 0x19, 0x79, 0x4a, 0xe4, 0x78, 0xd0, 0xb3, 0xfc, 0xff,
+ 0xd7, 0x8f, 0x35, 0x93, 0x41, 0x2d, 0x59, 0xf2, 0xd8, 0xcc,
+ 0x76, 0xb8, 0x3a, 0xf7, 0xdf, 0x1c, 0xa8, 0x85, 0xd5, 0xf8,
+ 0x54, 0x63, 0x75, 0x36, 0xea, 0x30, 0x5a, 0x13, 0x74, 0xf9,
+ 0x6d, 0x55, 0x27, 0x04, 0x0b, 0xa0, 0xa0, 0x6d, 0x9e, 0x3a,
+ 0x93, 0xbf, 0x4e, 0x92, 0x28, 0x85, 0xe4, 0xcf, 0xc5, 0x06,
+ 0xa7, 0x17, 0x5a, 0xec, 0xc2, 0x60, 0x6a, 0xab, 0x1b, 0x50,
+ 0xc9, 0x0b, 0x84, 0x9a, 0x82, 0x45, 0x1e, 0x29, 0xcc, 0x85,
+ 0x5a, 0xa8, 0xd4, 0x90, 0xfb, 0xa7, 0x2f, 0x89, 0xa7, 0x80,
+ 0xd0, 0x69, 0xb6, 0xca, 0xe0, 0x00, 0x4e, 0xe8, 0xea, 0x12,
+ 0x74, 0x85, 0xc8, 0x35, 0xea, 0x1d, 0x23, 0x75, 0x97, 0xcb,
+ 0x1d, 0x94, 0x6f, 0x2a, 0xa9, 0x7a, 0x32, 0xc5, 0x92, 0xf0,
+ 0x0a, 0x28, 0xbd, 0x77, 0xbc, 0xbe, 0xae, 0xa7, 0xa8, 0x34,
+ 0x96, 0x26, 0xaf, 0x83, 0x2a, 0x8e, 0xe9, 0xda, 0xad, 0xb2,
+ 0x52, 0x94, 0x74, 0x69, 0x70, 0x19, 0xc4, 0xc8, 0x36, 0xf3,
+ 0x0f, 0x32, 0x0f, 0x6f, 0x2b, 0x75, 0xac, 0x0e, 0x9d, 0x91,
+ 0x1f, 0x06, 0xba, 0xe3, 0x9c, 0x7f, 0xde, 0x16, 0x3a, 0x3f,
+ 0xfd, 0x7d, 0x88, 0x61, 0x90, 0xca, 0x98, 0xc1, 0xcf, 0xa5,
+ 0x50, 0x1f, 0xb3, 0x8c, 0xf9, 0x8c, 0xe9, 0x9f, 0xa3, 0xd3,
+ 0xd7, 0xce, 0x7f, 0xdf, 0xd2, 0x8e, 0x84, 0x9c, 0x24, 0xc3,
+ 0x85, 0xb1, 0xc6, 0x94, 0x8f, 0xbb, 0x55, 0x83, 0x8e, 0xa3,
+ 0xc7, 0xf5, 0x8e, 0x7e, 0xe6, 0x30, 0x13, 0x06, 0x61, 0x9e,
+ 0x63, 0x24, 0x27, 0x03, 0x01, 0xef, 0xec, 0x6d, 0xb1, 0x56,
+ 0x86, 0xd9, 0xf3, 0x2e, 0xfc, 0xe2, 0xbb, 0xa5, 0xa7, 0xaa,
+ 0x9c, 0x06, 0xf3, 0x36, 0x04, 0xae, 0xbd, 0x0f, 0x6f, 0xb9,
+ 0xc7, 0x42, 0x79, 0xfd, 0xac, 0x8f, 0x1c, 0xcc, 0x01, 0x9f,
+ 0x45, 0x3a, 0x52, 0xa2, 0x20, 0x31, 0xf6, 0x5d, 0xea, 0x4e,
+ 0xe7, 0x13, 0x9d, 0x79, 0xe8, 0x81, 0x62, 0xcd, 0xaf, 0x2e,
+ 0x6b, 0xb5, 0x1b, 0x75, 0xfe, 0x61, 0xf7, 0xcf, 0x4d, 0x90,
+ 0xdf, 0xe8, 0x56, 0x23, 0x96, 0xe4, 0xab, 0x5d, 0xa5, 0xd8,
+ 0x2e, 0x9e, 0x0c, 0x30, 0x27, 0xa3, 0x2c, 0x10, 0xda, 0x79,
+ 0xaf, 0xb9, 0x16, 0x6a, 0x89, 0x33, 0xe7, 0x46, 0xef, 0x43,
+ 0xe5, 0x5e, 0xb6, 0x38, 0xaa, 0x07, 0xb6, 0x5d, 0xd8, 0x57,
+ 0x4b, 0x94, 0xbd, 0xd0, 0x73, 0x9c, 0xf9, 0xa9, 0x31, 0x8a,
+ 0x71, 0xe5, 0xf0, 0xe6, 0xfb, 0x4e, 0xc4, 0xc7, 0x4b, 0xfe,
+ 0xe2, 0x1e, 0xbc, 0x77, 0xba, 0x3b, 0x2c, 0xa2, 0x71, 0x2e,
+ 0xf1, 0xd7, 0x33, 0xa2, 0xae, 0x2d, 0x89, 0x6a, 0xcb, 0x63,
+ 0xed, 0x8d, 0x83, 0x10, 0x96, 0xb4, 0x43, 0xef, 0xc2, 0xe6,
+ 0x49, 0x13, 0x2f, 0x0a, 0x09, 0xae, 0xca, 0xa8, 0x01, 0xff,
+ 0xc9, 0x65, 0x87, 0x8d, 0x20, 0x98, 0x5f, 0xfb, 0x25, 0xe0,
+ 0x7a, 0x3a, 0xcb, 0xf8, 0x48, 0x7e, 0x18, 0xcc, 0x4c, 0x66,
+ 0xd3, 0x76, 0x10, 0x93, 0xa2, 0x7d, 0x3b, 0x0d, 0x49, 0xca,
+ 0xa9, 0x18, 0x2a, 0x50, 0xe2, 0xb3, 0xdc, 0x77, 0x3c, 0x23,
+ 0x84, 0xfe, 0xe7, 0x86, 0x39, 0x7f, 0xc7, 0x18, 0xca, 0xae,
+ 0x79, 0xfc, 0x44, 0x27, 0xf9, 0xbc, 0xce, 0xdb, 0xda, 0xe9,
+ 0xd5, 0x83, 0x90, 0xfb, 0x8c, 0x79, 0x4b, 0xc9, 0x1b, 0x6b,
+ 0x57, 0x0a, 0x88, 0xfc, 0x80, 0x7d, 0x17, 0x10, 0x32, 0x7d,
+ 0xf5, 0x87, 0xf7, 0x18, 0xd6, 0x7a, 0x60, 0x5c, 0x2b, 0x4a,
+ 0x19, 0x0a, 0xea, 0x3f, 0xd1, 0x5b, 0x5e, 0x93, 0x38, 0x9f,
+ 0xc9, 0xfc, 0x6c, 0x67, 0x91, 0xd6, 0x0c, 0x3b, 0x1e, 0xa6,
+ 0x2d, 0x1a, 0x71, 0x74, 0x57, 0x67, 0x46, 0xa2, 0x93, 0xaf,
+ 0x9d, 0x85, 0x0e, 0x0b, 0x1d, 0x39, 0x84, 0xe1, 0x7c, 0x8a,
+ 0x11, 0xed, 0x7b, 0xfe, 0x98, 0xaf, 0x2b, 0x7a, 0xe6, 0xc6,
+ 0x70, 0xf7, 0xce, 0x13, 0xe6, 0x5a, 0x96, 0x7e, 0x5d, 0x76,
+ 0x30, 0xfc, 0x22, 0xdc, 0x4e, 0xb2, 0xd5, 0x59, 0x66, 0x54,
+ 0x70, 0x25, 0x83, 0x1f, 0x16, 0xf0, 0x0a, 0x72, 0xd5, 0xb1,
+ 0x69, 0x86, 0x13, 0xb8, 0x6c, 0x0f, 0xe0, 0x5a, 0xdd, 0x5e,
+ 0x26, 0x9a, 0x9e, 0xbc, 0x94, 0xdf, 0xd0, 0x08, 0x4f, 0xc9,
+ 0x87, 0xf3, 0xd5, 0x6d, 0xad, 0x65, 0xbd, 0x52, 0xd2, 0x10,
+ 0x71, 0xca, 0x4b, 0xe5, 0x72, 0xcd, 0xae, 0xa7, 0x9c, 0x51,
+ 0x52, 0x04, 0xe5, 0xcb, 0x80, 0xfa, 0x23, 0x1b, 0x4a, 0x0d,
+ 0x83, 0x8a, 0xa6, 0x91, 0xef, 0x10, 0x08, 0x05, 0x2a, 0x39,
+ 0x14, 0x2f, 0xdb, 0x1f, 0x96, 0x8d, 0xe1, 0x58, 0x08, 0xd0,
+ 0x65, 0x6d, 0x83, 0x8f, 0x71, 0xe0, 0x87, 0x90, 0x96, 0xce,
+ 0xa4, 0xa0, 0x30, 0xf7, 0x56, 0xd4, 0x88, 0x61, 0x08, 0x61,
+ 0xda, 0xd1, 0x36, 0x8a, 0x92, 0x3f, 0xb1, 0xd4, 0xd0, 0x6f,
+ 0x57, 0x9b, 0xb4, 0xe2, 0x96, 0x12, 0xad, 0x93, 0x4b, 0xd6,
+ 0x59, 0x37, 0x08, 0x4f, 0x7a, 0xc8, 0x7f, 0x37, 0x8e, 0x11,
+ 0x5f, 0x07, 0xd2, 0x5e, 0xbe, 0x54, 0xcb, 0xa8, 0xc6, 0x2e,
+ 0xfc, 0x23, 0x7e, 0xab, 0x40, 0x24, 0x19, 0x4c, 0x51, 0x19,
+ 0x52, 0xcc, 0xc2, 0x70, 0x63, 0xc6, 0x34, 0x1c, 0x97, 0x14,
+ 0xb1, 0xfe, 0xd3, 0xe6, 0x98, 0xf2, 0xd7, 0x77, 0x5a, 0x69,
+ 0xc6, 0xf6, 0x0f, 0xb9, 0x62, 0xae, 0x4a, 0xb0, 0x33, 0x3a,
+ 0x43, 0x16, 0x2b, 0x66, 0xbf, 0xc6, 0x8c, 0x41, 0x8d, 0x0f,
+ 0x94, 0x79, 0x77, 0x81, 0x01, 0x23, 0xd9, 0x5e, 0x81, 0x74,
+ 0x1d, 0x87, 0x11, 0x76, 0xc6, 0x0f, 0x40, 0x91, 0x72, 0xe9,
+ 0x7d, 0x51, 0xb8, 0x72, 0xa9, 0x6f, 0x23, 0x1d, 0x32, 0x33,
+ 0xde, 0xd8, 0xf7, 0x9c, 0x52, 0xa4, 0x6d, 0x4f, 0x3f, 0x9e,
+ 0xe4, 0xfa, 0x25, 0x8f, 0x27, 0x78, 0x8d, 0xde, 0x39, 0xa2,
+ 0xfd, 0xd1, 0x39, 0x01, 0xeb, 0xce, 0xac, 0x60, 0xe4, 0x9f,
+ 0x69, 0x8b, 0xf8, 0xd1, 0x85, 0x70, 0x74, 0xad, 0x1e, 0x30,
+ 0x89, 0x9f, 0x84, 0x58, 0xff, 0xd5, 0x84, 0x41, 0xa6, 0x68,
+ 0x16, 0xc8, 0x07, 0x9b, 0x7a, 0x33, 0x29, 0x05, 0x5c, 0x0e,
+ 0x08, 0x94, 0x4d, 0x18, 0x80, 0xba, 0x10, 0x82, 0x4e, 0x92,
+ 0x60, 0xa8, 0xff, 0x1a, 0x0e, 0x46, 0x57, 0x1e, 0x65, 0x67,
+ 0x25, 0x8d, 0x28, 0xb1, 0xdb, 0x66, 0xca, 0xc0, 0xbc, 0xa9,
+ 0x90, 0x8a, 0x74, 0x52, 0x97, 0x68, 0x32, 0x34, 0x60, 0xb0,
+ 0x30, 0x60, 0xd5, 0x89, 0x15, 0x09, 0x1f, 0x95, 0xc5, 0x41,
+ 0xe2, 0x02, 0x7a, 0x81, 0x7d, 0x49, 0xdd, 0x8c, 0x26, 0x05,
+ 0xfe, 0xed, 0xa8, 0x12, 0xa4, 0x3d, 0x82, 0x65, 0x3d, 0x87,
+ 0xed, 0x95, 0x86, 0x80, 0x3f, 0xc6, 0xcd, 0x27, 0x58, 0x56,
+ 0xca, 0x23, 0xca, 0xa4, 0xd8, 0x06, 0x11, 0xb4, 0x24, 0xc8,
+ 0x8a, 0xc5, 0x36, 0x42, 0xe3, 0xd4, 0xf7, 0xaf, 0x74, 0xac,
+ 0x89, 0xee, 0x47, 0x2e, 0x8b, 0xde, 0xd0, 0x85, 0xd4, 0x2e,
+ 0xb4, 0xae, 0x99, 0x96, 0x88, 0xe2, 0x60, 0x99, 0xfb, 0x62,
+ 0x5f, 0x8b, 0xa7, 0x24, 0xe2, 0xc3, 0x72, 0x4c, 0x6f, 0x1d,
+ 0xcd, 0xc1, 0x3a, 0x32, 0x06, 0x98, 0xb5, 0x92, 0x82, 0x98,
+ 0xb1, 0x7d, 0xca, 0xa1, 0xe1, 0x4d, 0xe1, 0xe5, 0xaf, 0x3a,
+ 0x08, 0x0d, 0x68, 0x33, 0xb8, 0xc0, 0xc3, 0xfd, 0xb9, 0x90,
+ 0x47, 0x58, 0xca, 0xb2, 0x8d, 0x2d, 0x75, 0x7d, 0xbb, 0xd3,
+ 0xc3, 0x0f, 0x7d, 0xa7, 0x5a, 0x5a, 0x25, 0x6f, 0x12, 0xd8,
+ 0x69, 0xd4, 0xc1, 0x20, 0x19, 0x65, 0x4d, 0x36, 0x7c, 0x7f,
+ 0xc4, 0x47, 0x00, 0xcb, 0x2c, 0x64, 0x13, 0x06, 0x0a, 0x37,
+ 0x1d, 0x1d, 0x7d, 0x89, 0x9c, 0x21, 0xd0, 0xaa, 0xa9, 0x01,
+ 0xee, 0xb2, 0x70, 0xc7, 0xc3, 0x11, 0x60, 0xd9, 0x73, 0xc9,
+ 0xea, 0x6d, 0x8a, 0x1b, 0x0f, 0x69, 0x1d, 0xb3, 0x95, 0x82,
+ 0x52, 0xf4, 0xa9, 0x4e, 0x25, 0x85, 0x75, 0xde, 0x12, 0x3c,
+ 0x11, 0x71, 0x06, 0x88, 0xf0, 0x39, 0x93, 0x29, 0x3a, 0x94,
+ 0x7c, 0x73, 0x42, 0xea, 0x59, 0x7c, 0x1d, 0x93, 0x1a, 0x14,
+ 0x10, 0x4c, 0x5e, 0x92, 0x1a, 0x9e, 0x71, 0xf1, 0x42, 0xb9,
+ 0x72, 0x13, 0x7d, 0x99, 0x4a, 0x28, 0x8b, 0x92, 0x48, 0x1d,
+ 0x78, 0x19, 0x04, 0xf8, 0xc4, 0xf3, 0xb1, 0x1c, 0x2f, 0x56,
+ 0xb6, 0xf6, 0x87, 0x00, 0xbd, 0x8c, 0x8c, 0x56, 0xe1, 0x38,
+ 0xf8, 0x6d, 0x19, 0xe7, 0x7e, 0x30, 0x52, 0xc6, 0x47, 0x72,
+ 0xc9, 0xa1, 0x30, 0xef, 0x8c, 0xf6, 0x23, 0xbf, 0xfb, 0xd0,
+ 0xbb, 0x51, 0xa9, 0xf6, 0x1d, 0xa7, 0xd2, 0xc7, 0xaa, 0xa6,
+ 0xc8, 0xa6, 0xa6, 0x61, 0x8f, 0x8c, 0x22, 0xee, 0x6e, 0xb5,
+ 0x21, 0x89, 0x72, 0x0e, 0xbe, 0x00, 0xcf, 0x08, 0x80, 0x09,
+ 0x06, 0x4d, 0xd4, 0xd4, 0xea, 0xb6, 0xa0, 0x8e, 0x71, 0xc8,
+ 0x73, 0x16, 0xdb, 0x55, 0x0f, 0xed, 0xbf, 0xb7, 0x40, 0x41,
+ 0x9a, 0x47, 0xfc, 0xad, 0x5b, 0xf4, 0x86, 0x3c, 0x98, 0xd1,
+ 0xaa, 0x51, 0x15, 0xc3, 0x10, 0x55, 0x8e, 0x5d, 0xf7, 0x9d,
+ 0xd4, 0x11, 0x35, 0xe7, 0x05, 0x11, 0x7d, 0x0f, 0x2b, 0x44,
+ 0xfe, 0x12, 0x7f, 0x39, 0xa6, 0x19, 0x91, 0x8b, 0xd0, 0xac,
+ 0x2c, 0x18, 0x63, 0x6a, 0x33, 0xc2, 0x75, 0xd2, 0xcd, 0xf7,
+ 0x22, 0x46, 0xca, 0xd1, 0xf9, 0xc0, 0x15, 0x08, 0xc7, 0x10,
+ 0x67, 0xd1, 0x42, 0x13, 0x27, 0x22, 0xc3, 0xe3, 0x94, 0x66,
+ 0x00, 0xbf, 0x2f, 0x7d, 0xfb, 0xc5, 0x6f, 0xb9, 0x03, 0x24,
+ 0xce, 0xfb, 0xf4, 0x6b, 0xad, 0xe0, 0x47, 0xda, 0x5f, 0xea,
+ 0x61, 0xfd, 0x6a, 0x76, 0x98, 0x15, 0x76, 0x72, 0xef, 0x95,
+ 0x77, 0x93, 0x3b, 0x23, 0xde, 0x09, 0x38, 0x1c, 0x7b, 0xd5,
+ 0xd1, 0x5c, 0xc5, 0xc4, 0x34, 0x0c, 0xd8, 0xc2, 0x05, 0xb7,
+ 0xe5, 0xb5, 0xc9, 0x7b, 0xc1, 0x28, 0xea, 0xc8, 0x07, 0x98,
+ 0xe2, 0xab, 0x73, 0x24, 0xfb, 0xef, 0xce, 0x0a, 0xa7, 0xb9,
+ 0x8d, 0xeb, 0x31, 0x09, 0x82, 0xf2, 0x9c, 0x4d, 0xf1, 0x1a,
+ 0x4a, 0x79, 0x35, 0xa7, 0x7b, 0x7b, 0xf0, 0xa9, 0xe7, 0xb9,
+ 0xf9, 0x52, 0x63, 0x73, 0x29, 0xe4, 0xd4, 0x13, 0x19, 0x2a,
+ 0xf2, 0x9d, 0x4e, 0xcc, 0x2a, 0xb3, 0xdf, 0x95, 0xc5, 0xb6,
+ 0x91, 0x34, 0xf4, 0x10, 0x2e, 0xe7, 0x0d, 0xf8, 0x3a, 0x6d,
+ 0xdd, 0x30, 0x57, 0xec, 0x05, 0x70, 0xef, 0x80, 0x8c, 0x8a,
+ 0xe1, 0x2e, 0x9e, 0xd4, 0xd1, 0xb4, 0xdc, 0x14, 0x87, 0xf5,
+ 0x97, 0x4b, 0x2c, 0xd9, 0x93, 0x71, 0x3a, 0x15, 0x25, 0xa0,
+ 0x8a, 0xb2, 0x93, 0xb5, 0x62, 0xf7, 0x81, 0x09, 0x9c, 0x8d,
+ 0xe5, 0x09, 0xb2, 0xda, 0x07, 0xf2, 0x33, 0xf6, 0x5c, 0xd2,
+ 0x71, 0xb1, 0xaf, 0xea, 0x62, 0x05, 0xf3, 0x5f, 0xd9, 0xb1,
+ 0x8e, 0xd1, 0x5d, 0xf9, 0x89, 0x85, 0x78, 0xd5, 0x54, 0x56,
+ 0x6c, 0xd4, 0x20, 0x45, 0xe7, 0x03, 0xcc, 0x25, 0x7e, 0xde,
+ 0x40, 0x3d, 0xb2, 0x46, 0x90, 0x59, 0xc2, 0xfc, 0xdc, 0x15,
+ 0xd4, 0x80, 0x5b, 0xd8, 0xec, 0x2e, 0x49, 0xd7, 0xd8, 0x45,
+ 0x0e, 0x7d, 0xba, 0x09, 0x32, 0x60, 0xdb, 0x64, 0xd6, 0x44,
+ 0xd7, 0xea, 0xb4, 0xb1, 0xb6, 0xfc, 0x1b, 0xde, 0x4a, 0x48,
+ 0xa1, 0x6c, 0x5b, 0x5a, 0x1f, 0x35, 0x55, 0x18, 0xd2, 0xae,
+ 0x88, 0x46, 0x5f, 0x2d, 0x25, 0xb0, 0xd9, 0x4f, 0x40, 0xbb,
+ 0x23, 0x40, 0x9f, 0x85, 0x19, 0x4e, 0xd4, 0xfc, 0xcd, 0xc4,
+ 0x59, 0x98, 0x27, 0xe2, 0x7e, 0xab, 0x36, 0x52, 0x51, 0x30,
+ 0xf4, 0xb4, 0xf3, 0xa1, 0x8d, 0x87, 0x01, 0xe5, 0x2d, 0x42,
+ 0x4b, 0x87, 0x70, 0x95, 0xde, 0x9d, 0x80, 0x36, 0x4e, 0x8a,
+ 0x14, 0x40, 0x1c, 0x27, 0x89, 0xe1, 0x42, 0xee, 0x77, 0x87,
+ 0x4f, 0x4d, 0xb0, 0x38, 0x3f, 0x05, 0x67, 0xe3, 0x9c, 0x88,
+ 0xd8, 0x68, 0x75, 0xf3, 0xf9, 0x92, 0x8e, 0x78, 0xbe, 0x43,
+ 0xb9, 0x28, 0xa9, 0x2e, 0xde, 0xc9, 0xbd, 0x48, 0x99, 0x4d,
+ 0x7d, 0xb6, 0xba, 0x3b, 0x31, 0x20, 0x9f, 0x0c, 0xc4, 0x90,
+ 0xae, 0x4a, 0x33, 0x17, 0x58, 0x13, 0xb5, 0xfd, 0xec, 0xef,
+ 0x87, 0x21, 0x0c, 0xce, 0xbd, 0xbd, 0x85, 0x61, 0x07, 0xb8,
+ 0x7f, 0x93, 0xaa, 0xbd, 0xc6, 0xae, 0x63, 0x2c, 0x12, 0x6a,
+ 0xaa, 0xe2, 0x9d, 0xf8, 0x55, 0xac, 0xc3, 0xa3, 0x40, 0xf8,
+ 0xad, 0xfb, 0x21, 0x89, 0x5c, 0x90, 0x21, 0x40, 0x57, 0xfb,
+ 0x08, 0x5d, 0x28, 0x83, 0xcc, 0x46, 0x76, 0x44, 0x2e, 0xe4,
+ 0x5b, 0xe7, 0x37, 0x79, 0xc7, 0xc8, 0x0a, 0x0b, 0xc2, 0xb6,
+ 0x17, 0x48, 0x8c, 0x2b, 0x67, 0xd6, 0xf9, 0x33, 0xe0, 0xbd,
+ 0x6b, 0xea, 0x79, 0x7d, 0x6d, 0x5b, 0x23, 0x7e, 0x5e, 0x5d,
+ 0x53, 0xcf, 0x01, 0xea, 0x86, 0x5f, 0x8a, 0x4b, 0x1f, 0x32,
+ 0xcd, 0xdb, 0x30, 0xe3, 0x27, 0x3f, 0x10, 0x4b, 0xba, 0x3c,
+ 0xf9, 0x42, 0x78, 0x52, 0x01, 0x87, 0xed, 0xf4, 0x15, 0xa4,
+ 0x8d, 0xa0, 0x1d, 0xc7, 0x89, 0xf5, 0x4f, 0x99, 0x28, 0x04,
+ 0xa7, 0x87, 0xf8, 0x9b, 0x77, 0x18, 0x0a, 0xb8, 0x59, 0xf3,
+ 0xcd, 0x74, 0xf1, 0xd5, 0x13, 0x61, 0xca, 0x02, 0x95, 0xe6,
+ 0x50, 0x30, 0x96, 0x83, 0x35, 0xf8, 0xd2, 0xea, 0xd7, 0xd6,
+ 0x5f, 0xbf, 0xab, 0x9b, 0x91, 0xc9, 0xc4, 0x93, 0x57, 0xb5,
+ 0x55, 0xb6, 0x83, 0xb0, 0x83, 0x17, 0x78, 0xc9, 0xb6, 0xa5,
+ 0x55, 0x9b, 0x2d, 0x08, 0x1f, 0x09, 0x37, 0x1c, 0x0a, 0x3a,
+ 0xdc, 0x32, 0xeb, 0x20
+ };
+
+ static const uint8_t keys_b_ps5[0x70 * 0x20] = {
+ 0xe1, 0x34, 0x17, 0x1d, 0xf5, 0x17, 0xda, 0xe1, 0x1b, 0xe6,
+ 0x0c, 0x73, 0x35, 0xd4, 0x6b, 0x42, 0x28, 0xcd, 0x47, 0x06,
+ 0x4e, 0x96, 0x87, 0xd5, 0x9d, 0x09, 0xe7, 0x24, 0xa0, 0x1b,
+ 0xad, 0x32, 0x6d, 0x06, 0x75, 0xdf, 0x77, 0x69, 0x89, 0xeb,
+ 0x03, 0x24, 0xd6, 0x31, 0x23, 0xc0, 0xf6, 0x0d, 0x60, 0x0c,
+ 0xb6, 0xad, 0x4b, 0xba, 0x9e, 0xb2, 0x66, 0xfd, 0x8d, 0xb5,
+ 0xdd, 0xc9, 0xf7, 0x67, 0xc3, 0x93, 0x3f, 0x1e, 0xc1, 0x87,
+ 0x5c, 0x34, 0xae, 0x24, 0x44, 0xc0, 0x34, 0xf3, 0xd9, 0x1e,
+ 0x3b, 0x62, 0x0b, 0xa0, 0x00, 0xa2, 0x1e, 0x13, 0x9b, 0xa6,
+ 0xe8, 0x8b, 0x91, 0xb1, 0x42, 0xb1, 0x2b, 0xa7, 0xce, 0xc6,
+ 0x85, 0x6e, 0xe9, 0x22, 0xfc, 0x6d, 0x77, 0xb2, 0x9b, 0x71,
+ 0x5d, 0x1a, 0xf4, 0xb6, 0x93, 0x72, 0xae, 0x46, 0x40, 0x5d,
+ 0x26, 0x30, 0x98, 0xf8, 0xc7, 0xbe, 0xb7, 0x2c, 0x6e, 0x1b,
+ 0x13, 0x32, 0x7a, 0xb2, 0x81, 0xa6, 0x96, 0xc4, 0xe8, 0x15,
+ 0x82, 0xc4, 0xea, 0xc3, 0x28, 0x70, 0xaf, 0x3f, 0xf6, 0x7f,
+ 0x13, 0xa6, 0x73, 0x27, 0x29, 0xdf, 0x17, 0x38, 0xd4, 0xb0,
+ 0x72, 0xed, 0x91, 0xb5, 0xb1, 0xe6, 0xd7, 0x3a, 0x34, 0xa9,
+ 0x67, 0x45, 0x67, 0x02, 0xc2, 0xc6, 0x3c, 0x22, 0xf0, 0x22,
+ 0xec, 0xff, 0x7d, 0xda, 0x2c, 0xe4, 0x67, 0x47, 0x6b, 0x31,
+ 0x63, 0xf4, 0xb3, 0x5d, 0xdb, 0x47, 0x33, 0xa6, 0x1c, 0x21,
+ 0xa3, 0x48, 0x0f, 0x7f, 0xc9, 0x84, 0x1a, 0x57, 0x0d, 0xd5,
+ 0x72, 0xd6, 0x20, 0x34, 0x36, 0x41, 0x0e, 0x43, 0x11, 0xe5,
+ 0x43, 0x91, 0x05, 0x69, 0xcf, 0x79, 0x70, 0x90, 0xc5, 0x7a,
+ 0xf3, 0xb1, 0x3d, 0x59, 0x08, 0x59, 0xe1, 0x41, 0xae, 0x65,
+ 0x2e, 0x5c, 0xcb, 0x04, 0x8d, 0x4c, 0xac, 0x50, 0x21, 0x93,
+ 0x06, 0x36, 0xb6, 0xe0, 0xb1, 0xdc, 0x4c, 0x98, 0x48, 0x8f,
+ 0x95, 0x12, 0x64, 0x40, 0x87, 0x6b, 0xdd, 0xf7, 0x79, 0x0a,
+ 0xc1, 0x1d, 0x67, 0xf9, 0xf5, 0xf8, 0xce, 0x75, 0x94, 0x0a,
+ 0x66, 0x17, 0xec, 0x17, 0xee, 0xfa, 0xf6, 0x88, 0x8b, 0xeb,
+ 0x2e, 0x06, 0xf1, 0x77, 0x1d, 0x22, 0xd3, 0x4c, 0x4f, 0x8d,
+ 0xa8, 0x6a, 0x53, 0xc7, 0xd4, 0x72, 0xb4, 0xec, 0x97, 0x9d,
+ 0x31, 0x48, 0xed, 0x62, 0xd2, 0x40, 0x94, 0x23, 0x4c, 0x10,
+ 0x37, 0x20, 0x3b, 0xb6, 0x9f, 0x1c, 0x99, 0xcf, 0xce, 0x9d,
+ 0xda, 0xe7, 0xc9, 0x32, 0x88, 0xe9, 0x1d, 0x37, 0xb5, 0xa7,
+ 0x8b, 0x19, 0x8a, 0x50, 0x50, 0x2d, 0xa9, 0x89, 0x4e, 0x6e,
+ 0x7d, 0xbf, 0xd2, 0x7e, 0xe1, 0xa2, 0x6b, 0x3e, 0xf1, 0x33,
+ 0x0e, 0x74, 0x88, 0x96, 0xd8, 0xa3, 0x00, 0x2d, 0x9b, 0xaf,
+ 0x71, 0xac, 0x68, 0x63, 0x2f, 0xa9, 0x46, 0x6c, 0x73, 0x5d,
+ 0xb0, 0xd6, 0x92, 0xa3, 0x03, 0xc4, 0x93, 0xb6, 0x48, 0x7f,
+ 0x3c, 0x15, 0x1b, 0xb1, 0xe0, 0x40, 0x56, 0x8f, 0x63, 0xa6,
+ 0x94, 0xc9, 0x8b, 0xde, 0x66, 0x2a, 0x5f, 0x32, 0x4a, 0x8d,
+ 0xa6, 0x50, 0xdf, 0xdf, 0xaa, 0x20, 0xe1, 0xeb, 0x71, 0x05,
+ 0xd7, 0xca, 0xe1, 0xa8, 0x79, 0xbc, 0x83, 0x94, 0x74, 0x85,
+ 0x58, 0x89, 0xc8, 0x59, 0x97, 0xaa, 0xf0, 0xfa, 0x5a, 0x37,
+ 0xa7, 0x4a, 0x13, 0xe6, 0x36, 0x0f, 0xe6, 0x45, 0x29, 0x89,
+ 0xb6, 0x11, 0x32, 0xdb, 0x0d, 0x75, 0x58, 0xe2, 0x9a, 0x1a,
+ 0x1b, 0x08, 0x66, 0x2a, 0x3d, 0x66, 0xe1, 0x24, 0x82, 0xe6,
+ 0x0b, 0xc5, 0xe6, 0xd7, 0x9c, 0x18, 0xa3, 0x4d, 0x2f, 0x4e,
+ 0x46, 0xd6, 0x21, 0x76, 0xaf, 0xad, 0xa9, 0xd8, 0x37, 0xd1,
+ 0x3d, 0xfe, 0x32, 0x24, 0xdc, 0xc4, 0xbe, 0x69, 0x2e, 0x97,
+ 0x8f, 0x09, 0xb1, 0x55, 0x9b, 0x3d, 0xe2, 0x46, 0x1c, 0x99,
+ 0x27, 0x4d, 0x06, 0xff, 0x72, 0x7a, 0xef, 0xe9, 0x9a, 0xc4,
+ 0xe9, 0x93, 0xc4, 0xf0, 0xb6, 0x64, 0xa9, 0xea, 0xf2, 0x09,
+ 0xd6, 0xa3, 0x20, 0x6c, 0xd1, 0x1b, 0xd7, 0x7f, 0x74, 0x00,
+ 0xf5, 0x60, 0xf8, 0xf4, 0x99, 0x93, 0xa5, 0xc2, 0x2b, 0xb1,
+ 0xe9, 0x83, 0x39, 0x33, 0x4d, 0x16, 0x0b, 0xb8, 0xa6, 0x6d,
+ 0x30, 0x9b, 0x97, 0xbe, 0x9f, 0x0c, 0x1e, 0x4e, 0xff, 0x3a,
+ 0xe2, 0x06, 0x15, 0xba, 0x57, 0x9d, 0x60, 0x76, 0x4d, 0x50,
+ 0x88, 0x6d, 0x8b, 0x9d, 0xf9, 0x70, 0x3d, 0x66, 0x87, 0x11,
+ 0x1e, 0x9a, 0x24, 0xaa, 0xa5, 0xe8, 0xf8, 0x38, 0x6c, 0x3e,
+ 0x6d, 0xda, 0xe5, 0xfc, 0xde, 0x9e, 0x2a, 0xe4, 0xe6, 0xee,
+ 0x07, 0xce, 0x48, 0x5e, 0xd8, 0x27, 0x23, 0x10, 0xc9, 0x72,
+ 0x9d, 0x7c, 0xd9, 0x5d, 0xa8, 0xba, 0x8a, 0x1e, 0x22, 0xef,
+ 0x46, 0x22, 0x6f, 0x79, 0x2e, 0x6f, 0x73, 0xbf, 0xcd, 0xcf,
+ 0x4f, 0x13, 0xdc, 0x5d, 0x51, 0x0b, 0xd5, 0xa9, 0x76, 0xc5,
+ 0xe3, 0xc0, 0x90, 0xe9, 0x32, 0x28, 0xfc, 0x9f, 0xf1, 0x88,
+ 0x1f, 0xa6, 0xb3, 0x45, 0x04, 0xa7, 0x1c, 0xbd, 0x66, 0xe7,
+ 0xbb, 0xc1, 0x49, 0x42, 0xdb, 0x52, 0x4b, 0xbb, 0xd9, 0x63,
+ 0x8f, 0x6d, 0x9d, 0x3b, 0xd7, 0xe3, 0x49, 0xbc, 0x7c, 0xd9,
+ 0xdb, 0x50, 0x67, 0x6f, 0x07, 0x6d, 0x08, 0x2a, 0xea, 0x02,
+ 0xeb, 0xbe, 0x22, 0xd1, 0x17, 0x1b, 0xfa, 0x39, 0x1c, 0x6c,
+ 0xda, 0x56, 0x37, 0xd3, 0x8e, 0xf4, 0xe3, 0x22, 0xaa, 0x6c,
+ 0xc1, 0x75, 0x71, 0x4c, 0x21, 0x36, 0xfb, 0xe0, 0x02, 0x54,
+ 0x6a, 0x48, 0x31, 0x40, 0x7b, 0x64, 0x7c, 0x76, 0x31, 0xe5,
+ 0x0f, 0xc1, 0xd8, 0xb4, 0x29, 0xd9, 0x9c, 0x0c, 0x80, 0xce,
+ 0xa3, 0x7c, 0x2f, 0x0f, 0x4c, 0x11, 0xb5, 0x3d, 0x85, 0x93,
+ 0xbd, 0x60, 0xd4, 0x5c, 0x3f, 0x41, 0x54, 0xe6, 0x50, 0xb1,
+ 0xb1, 0xed, 0x85, 0x19, 0x30, 0x81, 0x6a, 0x95, 0xfe, 0x4a,
+ 0x93, 0x28, 0x12, 0x3d, 0xdc, 0x76, 0xac, 0x59, 0xe6, 0xe3,
+ 0x56, 0x81, 0x1d, 0x29, 0xf9, 0x65, 0x48, 0xc3, 0x70, 0x50,
+ 0x2f, 0x2c, 0x4c, 0x69, 0x01, 0xdc, 0x75, 0x3d, 0x14, 0xff,
+ 0x5f, 0xaf, 0x6a, 0xc7, 0xc1, 0xa3, 0x3b, 0x12, 0xa5, 0x6d,
+ 0xd7, 0x95, 0xdd, 0x3d, 0x70, 0x67, 0xcb, 0x2f, 0x22, 0x5d,
+ 0xac, 0x9d, 0xf2, 0x1c, 0x6a, 0x27, 0x45, 0x59, 0x29, 0x5a,
+ 0x4f, 0x6c, 0x34, 0x43, 0x43, 0x94, 0xd1, 0xe6, 0x1d, 0xde,
+ 0xda, 0x7c, 0x4f, 0xdf, 0xf6, 0xa1, 0x42, 0xde, 0x88, 0xc7,
+ 0xcc, 0xbb, 0x99, 0xb0, 0x49, 0xa5, 0x9d, 0x6a, 0xae, 0x3f,
+ 0xb0, 0x59, 0x2c, 0xfa, 0xb8, 0x66, 0x26, 0xf5, 0xdb, 0xb7,
+ 0xe8, 0xc5, 0xb3, 0x45, 0xfa, 0xd2, 0xd3, 0xec, 0x1b, 0xcf,
+ 0x83, 0xc5, 0x96, 0x66, 0x92, 0x27, 0xd4, 0x88, 0xbf, 0x3a,
+ 0x7e, 0x00, 0x22, 0xb2, 0x8a, 0x80, 0xfe, 0xf9, 0x75, 0x1a,
+ 0xd1, 0xcc, 0xaf, 0x97, 0x9f, 0x6c, 0xd6, 0x7f, 0x1f, 0x6e,
+ 0xe1, 0x5f, 0x77, 0x2b, 0x96, 0xd6, 0x21, 0x73, 0xf9, 0x6a,
+ 0x8e, 0x99, 0x65, 0x19, 0x0f, 0x91, 0xe7, 0x51, 0x57, 0x2c,
+ 0x59, 0x7b, 0xdc, 0xb2, 0x84, 0x06, 0x12, 0x6d, 0xf9, 0x61,
+ 0x4c, 0xa7, 0x4f, 0x2c, 0x8a, 0x58, 0xfb, 0xc3, 0x6c, 0x90,
+ 0x9b, 0xc3, 0x19, 0x72, 0x34, 0x44, 0x9b, 0x2f, 0x53, 0xc3,
+ 0x6f, 0x4e, 0xf7, 0xff, 0x7f, 0x22, 0x04, 0xd4, 0x5c, 0xd7,
+ 0x18, 0x07, 0x29, 0xc8, 0x3e, 0x2b, 0xbe, 0xa9, 0xba, 0x17,
+ 0x3b, 0xcb, 0xa8, 0xb0, 0x63, 0x90, 0x09, 0x22, 0x4d, 0xbf,
+ 0x54, 0x31, 0x4f, 0x41, 0x8b, 0xaa, 0x8b, 0x8e, 0x19, 0x07,
+ 0x60, 0x62, 0xdc, 0x4b, 0x99, 0x64, 0x95, 0xea, 0xf6, 0xcc,
+ 0xfc, 0x97, 0xe5, 0x7f, 0xd4, 0xd9, 0xb9, 0x9b, 0x5a, 0x28,
+ 0x05, 0x8f, 0x30, 0x7d, 0x5f, 0x11, 0x55, 0x62, 0x64, 0x98,
+ 0x9c, 0x2a, 0x6b, 0x6e, 0x64, 0xac, 0x3f, 0x1f, 0xbc, 0x45,
+ 0x98, 0x0e, 0xed, 0x51, 0x92, 0x19, 0x84, 0x11, 0xb8, 0x8f,
+ 0xe7, 0xb7, 0x7a, 0x9b, 0xc0, 0x41, 0x50, 0x85, 0xc9, 0xc2,
+ 0x0e, 0xe9, 0x84, 0xb5, 0xcc, 0x72, 0xa6, 0xec, 0xd4, 0xdd,
+ 0x6f, 0x2f, 0xd0, 0xb6, 0xa6, 0x03, 0xaa, 0xd3, 0x99, 0x1c,
+ 0xb7, 0x5f, 0x7a, 0xda, 0x9f, 0xe7, 0xff, 0xa5, 0x8e, 0x77,
+ 0x4c, 0x14, 0x85, 0xd0, 0x78, 0x3c, 0x0f, 0xaf, 0x1c, 0x5c,
+ 0x09, 0x79, 0x6c, 0xb4, 0x64, 0xa1, 0x86, 0xd0, 0xb2, 0xfa,
+ 0xeb, 0xfc, 0xda, 0xf3, 0x42, 0x3a, 0x43, 0xb0, 0xd2, 0x24,
+ 0xfe, 0x04, 0xd1, 0x31, 0x47, 0x77, 0xde, 0x0f, 0xb4, 0x55,
+ 0x28, 0x83, 0x14, 0x5e, 0xd5, 0x4d, 0x99, 0xce, 0xf1, 0xb9,
+ 0x48, 0x6e, 0xf8, 0x17, 0xd5, 0x0a, 0xa0, 0xa3, 0x7a, 0xce,
+ 0x9a, 0x1a, 0x29, 0x83, 0xfc, 0x42, 0x58, 0x73, 0x48, 0x2c,
+ 0x18, 0xdd, 0x46, 0xb6, 0x0f, 0x5e, 0xbe, 0xa3, 0x3e, 0x2e,
+ 0xa0, 0xd6, 0xef, 0xe8, 0xf8, 0x88, 0xd8, 0x04, 0x44, 0x53,
+ 0x09, 0x33, 0x07, 0xfc, 0x70, 0x05, 0xc7, 0x4e, 0x56, 0xea,
+ 0x27, 0xbe, 0x15, 0x6c, 0x0a, 0xa7, 0x04, 0x5a, 0x35, 0x36,
+ 0x1c, 0x9e, 0xeb, 0x88, 0xee, 0xdd, 0x63, 0x4c, 0x34, 0xa9,
+ 0x70, 0x93, 0xc8, 0xea, 0x83, 0x79, 0x6f, 0x0e, 0x25, 0xc5,
+ 0x0b, 0x32, 0x55, 0xbc, 0x0a, 0x75, 0x90, 0x82, 0xa2, 0x18,
+ 0x2f, 0x12, 0x26, 0x31, 0x92, 0xeb, 0x47, 0x38, 0x60, 0x62,
+ 0xe9, 0xce, 0xb0, 0x42, 0x88, 0xe6, 0x84, 0x3c, 0x13, 0x37,
+ 0x4d, 0x2b, 0x74, 0x01, 0xef, 0x07, 0xb6, 0x9e, 0x99, 0x2a,
+ 0x84, 0x4b, 0x6f, 0x41, 0x9a, 0x8b, 0x0e, 0x01, 0xbb, 0x51,
+ 0xf9, 0x73, 0x7b, 0x7a, 0x51, 0x50, 0x68, 0x8a, 0x41, 0xbe,
+ 0xb6, 0x91, 0xf6, 0x5d, 0xe9, 0x66, 0xef, 0xfd, 0xf3, 0x8e,
+ 0xe2, 0x24, 0xcb, 0xca, 0x5b, 0xc5, 0x75, 0x87, 0x3f, 0x15,
+ 0x6e, 0xd3, 0xc0, 0x6f, 0xe2, 0x61, 0xab, 0x97, 0x7d, 0x22,
+ 0x67, 0xef, 0x3b, 0x84, 0xb3, 0xe7, 0x6c, 0x99, 0x77, 0x2f,
+ 0xed, 0xa8, 0xd1, 0xf6, 0x60, 0x38, 0x85, 0x1a, 0xef, 0x91,
+ 0x7d, 0x40, 0xa9, 0x3a, 0x6e, 0x45, 0xe0, 0x99, 0x83, 0x80,
+ 0x74, 0x02, 0xe3, 0x04, 0xe6, 0x7a, 0x64, 0xc9, 0x08, 0x8a,
+ 0xa4, 0x9e, 0x64, 0x01, 0xce, 0xa1, 0xb8, 0x3b, 0xf6, 0x22,
+ 0x0d, 0x8c, 0x7b, 0xf8, 0xfc, 0xd8, 0xf0, 0x92, 0xdc, 0x95,
+ 0xdd, 0xa8, 0x05, 0xde, 0x59, 0x2f, 0xc2, 0x17, 0x8d, 0xfa,
+ 0x6f, 0x7b, 0x2d, 0x07, 0x4d, 0x53, 0x3e, 0x80, 0x6c, 0x5d,
+ 0x62, 0xe4, 0xe1, 0x9f, 0x93, 0x8a, 0x2e, 0x59, 0xde, 0xad,
+ 0x44, 0xec, 0x47, 0x12, 0x68, 0xfd, 0x6f, 0x87, 0x14, 0xd5,
+ 0x0c, 0xe1, 0x93, 0xec, 0xc0, 0xa1, 0x93, 0x4d, 0x2d, 0xff,
+ 0x27, 0x00, 0x18, 0xe2, 0xb3, 0x40, 0xdf, 0x1d, 0x56, 0x05,
+ 0xb6, 0x42, 0x3c, 0x76, 0xde, 0xd2, 0x0d, 0xa6, 0xaf, 0x8b,
+ 0x71, 0x93, 0x7d, 0x92, 0x3f, 0x55, 0xf0, 0x99, 0x34, 0x26,
+ 0xa2, 0xa5, 0xc8, 0x15, 0xe9, 0x91, 0x6f, 0xd7, 0xc1, 0xc7,
+ 0xe6, 0xc5, 0xa1, 0xba, 0x08, 0x8e, 0x78, 0xac, 0x3b, 0xd2,
+ 0xd3, 0xc4, 0x46, 0x99, 0x97, 0x7d, 0x71, 0x27, 0xc9, 0x2f,
+ 0x15, 0x7c, 0x65, 0xc2, 0x2b, 0x40, 0xc8, 0x6c, 0x05, 0x8b,
+ 0x6f, 0x1e, 0xfc, 0x1f, 0x78, 0xd8, 0x48, 0x9a, 0x2f, 0xb4,
+ 0x38, 0xb7, 0xfc, 0x92, 0xdb, 0x97, 0x4d, 0x03, 0x4c, 0xf9,
+ 0xba, 0x69, 0x3f, 0x41, 0xbb, 0x39, 0xba, 0x3e, 0x74, 0xac,
+ 0x8e, 0x57, 0x8d, 0x45, 0x6b, 0x9d, 0x8a, 0xa1, 0x16, 0xb0,
+ 0xea, 0x7d, 0x12, 0xe5, 0x08, 0x0d, 0xc2, 0xba, 0x4f, 0x52,
+ 0xdb, 0xb8, 0xb6, 0x4c, 0x07, 0xb2, 0xdc, 0xbc, 0xc7, 0x77,
+ 0x01, 0xb0, 0xda, 0xfd, 0x15, 0x2b, 0x5e, 0xce, 0x5d, 0x2c,
+ 0x6b, 0x55, 0xbb, 0x5b, 0x44, 0xd1, 0xf8, 0x36, 0x9b, 0x81,
+ 0x19, 0x3a, 0x47, 0x18, 0x93, 0x23, 0x40, 0xaa, 0x14, 0x7f,
+ 0xe3, 0xb5, 0xd6, 0x1e, 0x70, 0x26, 0x85, 0xb3, 0x51, 0x27,
+ 0x1c, 0x45, 0xca, 0xb7, 0x47, 0xa9, 0x98, 0xf3, 0xb3, 0xa9,
+ 0x37, 0x33, 0xbf, 0x10, 0x82, 0x95, 0xce, 0xa9, 0xa4, 0x57,
+ 0x9c, 0xec, 0xde, 0xfb, 0x2b, 0x2b, 0xd8, 0xc7, 0x6d, 0xe4,
+ 0x4c, 0x29, 0x7e, 0x6e, 0xfb, 0x26, 0xe6, 0x42, 0x17, 0xdf,
+ 0xd0, 0x1f, 0xc2, 0xb7, 0x71, 0xd8, 0x44, 0x6b, 0xf6, 0x00,
+ 0x78, 0x51, 0xa0, 0x57, 0x28, 0x75, 0x00, 0x0e, 0x4f, 0xac,
+ 0x3d, 0xe6, 0x38, 0x75, 0xa0, 0x1a, 0xcf, 0x42, 0x20, 0xa0,
+ 0x22, 0x83, 0x80, 0xb3, 0xf6, 0x5f, 0x15, 0x85, 0xb5, 0x8d,
+ 0xd7, 0xae, 0xf2, 0xb8, 0xb1, 0xc1, 0xb7, 0xd6, 0xe8, 0x9f,
+ 0x6a, 0x68, 0x31, 0x7d, 0xbe, 0xf3, 0x75, 0xaa, 0x24, 0xc3,
+ 0x41, 0xa7, 0xfc, 0xaf, 0xd3, 0xd1, 0x71, 0xb0, 0x60, 0xcc,
+ 0x35, 0xbe, 0x09, 0x49, 0x7f, 0xf6, 0x97, 0x0b, 0xf1, 0x6b,
+ 0xe5, 0x2d, 0xec, 0x83, 0x53, 0x8a, 0x20, 0xb9, 0x96, 0xbb,
+ 0x61, 0x1e, 0x47, 0x32, 0x02, 0x2a, 0xe5, 0x1c, 0xa8, 0x9f,
+ 0xec, 0xbe, 0x2b, 0x46, 0xa6, 0x14, 0x9b, 0x84, 0xed, 0x92,
+ 0x90, 0xee, 0x08, 0x65, 0x4b, 0x06, 0x99, 0x23, 0x84, 0xba,
+ 0x97, 0xc1, 0x8c, 0x42, 0xde, 0x2e, 0x95, 0xe4, 0x9d, 0xe0,
+ 0x6f, 0x8d, 0x15, 0x89, 0xa9, 0x8b, 0x74, 0x2a, 0xf9, 0x4b,
+ 0xc7, 0x76, 0xd4, 0xf5, 0x53, 0xae, 0x95, 0x44, 0x4f, 0x61,
+ 0x86, 0x12, 0x9f, 0xf2, 0xe2, 0x37, 0x41, 0x09, 0x86, 0x52,
+ 0x3f, 0xac, 0xa4, 0x98, 0x4e, 0x42, 0x6c, 0x81, 0xc0, 0x82,
+ 0x82, 0xc2, 0x4c, 0xca, 0x80, 0x77, 0xd1, 0x08, 0x3b, 0x90,
+ 0x63, 0x97, 0x8c, 0x80, 0xab, 0x48, 0xba, 0x1b, 0x59, 0x77,
+ 0x37, 0x78, 0xb0, 0x95, 0xd2, 0x5b, 0xab, 0xd8, 0xf6, 0xf1,
+ 0xcf, 0x40, 0x1a, 0x6e, 0x40, 0x61, 0x95, 0x39, 0x2a, 0x17,
+ 0x58, 0x51, 0xda, 0xeb, 0x4c, 0x08, 0xf1, 0x94, 0xed, 0x44,
+ 0x48, 0xbb, 0xaa, 0xdd, 0x78, 0x76, 0x30, 0x31, 0x86, 0xfd,
+ 0x91, 0xd7, 0xf8, 0xd9, 0xd4, 0x1a, 0x5a, 0x92, 0x3f, 0x16,
+ 0x11, 0x48, 0xf8, 0x39, 0x79, 0x63, 0xa2, 0x00, 0x94, 0x13,
+ 0x70, 0xe8, 0xc1, 0x7e, 0x93, 0xf7, 0x39, 0x58, 0xee, 0x3b,
+ 0xfa, 0x81, 0xe6, 0xc3, 0x64, 0x31, 0x6b, 0xfd, 0xf8, 0x01,
+ 0x75, 0x14, 0xac, 0xb5, 0x18, 0x2f, 0xef, 0xd5, 0x42, 0x4d,
+ 0xa1, 0xa5, 0x46, 0x92, 0x5b, 0x37, 0x3d, 0x3f, 0x99, 0xd2,
+ 0x23, 0xd8, 0x75, 0x10, 0x81, 0x76, 0x91, 0xdb, 0x3d, 0xf3,
+ 0x19, 0xbf, 0xfc, 0xfc, 0xb1, 0x6c, 0x3b, 0x6a, 0x67, 0xf0,
+ 0xb9, 0x0a, 0x6e, 0xc2, 0xc6, 0x12, 0x88, 0x54, 0x78, 0x66,
+ 0x68, 0xb6, 0xe7, 0x81, 0x39, 0x6f, 0x18, 0x45, 0x34, 0x4c,
+ 0x99, 0xaa, 0x4d, 0x23, 0xf8, 0x5f, 0xcb, 0xe7, 0xec, 0xbe,
+ 0xbe, 0xf3, 0x37, 0x93, 0x96, 0x7d, 0x4f, 0x57, 0x95, 0xf5,
+ 0xea, 0x6a, 0xca, 0xd1, 0x4e, 0x8a, 0xf0, 0x46, 0x34, 0xc5,
+ 0x79, 0x78, 0xce, 0x79, 0x47, 0x9e, 0xb7, 0x92, 0x51, 0x2d,
+ 0x8f, 0x5d, 0xab, 0x9a, 0x5e, 0x4a, 0x2d, 0x39, 0x2c, 0x11,
+ 0x24, 0x22, 0x0f, 0xac, 0x65, 0xbf, 0xd6, 0xda, 0xef, 0x82,
+ 0x00, 0xfb, 0x40, 0x53, 0x55, 0xf7, 0xe5, 0xa5, 0x6a, 0x84,
+ 0xc6, 0x36, 0x1a, 0x72, 0xa6, 0x2a, 0xe0, 0xb2, 0xa3, 0xfe,
+ 0x1f, 0x32, 0x9c, 0xff, 0x8a, 0x3c, 0xcc, 0xd1, 0xf0, 0x8c,
+ 0x99, 0xbe, 0xd1, 0x82, 0xb6, 0x74, 0x88, 0x82, 0x99, 0x6e,
+ 0xdb, 0xd0, 0xe2, 0x02, 0x3b, 0xee, 0x44, 0x23, 0x70, 0x14,
+ 0x1a, 0x37, 0x6b, 0x09, 0xb2, 0x0d, 0x79, 0x4b, 0xf6, 0xa3,
+ 0x63, 0x9c, 0xb3, 0x29, 0x29, 0xc6, 0xc9, 0x28, 0x0a, 0xce,
+ 0xf0, 0x96, 0xf9, 0xbb, 0x3b, 0x7a, 0xad, 0x7e, 0x12, 0x17,
+ 0xb6, 0xc6, 0x5f, 0x16, 0x23, 0xa7, 0xbf, 0xaa, 0x48, 0x60,
+ 0x60, 0x83, 0xdd, 0xf3, 0x2a, 0xdc, 0x92, 0x11, 0x62, 0x2c,
+ 0x8d, 0xd8, 0xb6, 0x77, 0x6e, 0x1a, 0x2a, 0xa5, 0x34, 0xda,
+ 0xad, 0x75, 0x64, 0x88, 0x73, 0x3f, 0x51, 0xfe, 0xca, 0x9f,
+ 0xe9, 0x65, 0x4a, 0x1e, 0xb6, 0x81, 0x05, 0xb8, 0x61, 0x01,
+ 0x1e, 0x36, 0x41, 0x6e, 0xd6, 0x70, 0xe3, 0xc9, 0xa4, 0xe7,
+ 0x1e, 0x6c, 0x55, 0xc6, 0xe8, 0xbf, 0xfe, 0xcf, 0x1e, 0x23,
+ 0x99, 0xca, 0xf0, 0xa7, 0x57, 0x3e, 0x8f, 0xf5, 0x6f, 0x0e,
+ 0xe9, 0xbc, 0xef, 0x28, 0x13, 0xe8, 0xf7, 0x2f, 0x5e, 0xc2,
+ 0xbe, 0xac, 0x79, 0x37, 0xad, 0xbb, 0xe2, 0x01, 0xf4, 0x63,
+ 0x7f, 0xef, 0x9a, 0xbc, 0x9b, 0xa7, 0x5a, 0x70, 0xfa, 0x02,
+ 0xc6, 0xa4, 0x4a, 0xbb, 0xab, 0xed, 0x41, 0x6c, 0x5a, 0x38,
+ 0xbf, 0xdb, 0xb1, 0x6d, 0xd7, 0xe2, 0x3a, 0xfe, 0x23, 0x3f,
+ 0x6c, 0xb0, 0x18, 0xce, 0xc8, 0xed, 0x82, 0x5d, 0x4d, 0x18,
+ 0x43, 0x89, 0x57, 0x06, 0xfd, 0xc2, 0x94, 0x0f, 0x95, 0xda,
+ 0x43, 0xa7, 0xbc, 0xc7, 0x61, 0x39, 0x71, 0x8f, 0x9d, 0x4c,
+ 0x1c, 0xe5, 0x80, 0x2d, 0x37, 0xd4, 0xd1, 0x4e, 0x9a, 0xa6,
+ 0x69, 0x44, 0xdf, 0xa3, 0x3d, 0x7b, 0x8c, 0x56, 0x71, 0x0d,
+ 0xf0, 0x7c, 0xbc, 0xcc, 0xde, 0x2c, 0x00, 0x3c, 0xe6, 0x44,
+ 0x75, 0xbe, 0x74, 0x23, 0x97, 0xdb, 0xde, 0x19, 0xb8, 0x43,
+ 0x3b, 0x20, 0x40, 0x3a, 0xa0, 0xde, 0x9c, 0x5e, 0x0d, 0x74,
+ 0x88, 0x9f, 0xd6, 0x37, 0x3d, 0x0f, 0xce, 0xac, 0xed, 0xb0,
+ 0x84, 0xf6, 0xe5, 0x23, 0x90, 0x04, 0x79, 0x27, 0x42, 0x01,
+ 0x9e, 0x40, 0x02, 0x72, 0xe1, 0xee, 0x80, 0x95, 0x69, 0x6f,
+ 0x44, 0x0c, 0x5c, 0x82, 0x23, 0x2b, 0x43, 0x7f, 0x55, 0xec,
+ 0xf8, 0xfb, 0x23, 0xed, 0x49, 0x92, 0x1d, 0xcf, 0xef, 0xcf,
+ 0x02, 0x04, 0xd8, 0x69, 0x7f, 0x3f, 0xf8, 0x8b, 0xee, 0x6f,
+ 0x30, 0x4d, 0x4b, 0x4e, 0xdb, 0x8a, 0xfc, 0xd3, 0xb5, 0x00,
+ 0x80, 0x52, 0x99, 0x9c, 0xab, 0x46, 0x94, 0x34, 0xca, 0x0e,
+ 0x30, 0xae, 0x0e, 0xfb, 0xfa, 0x18, 0x63, 0x8a, 0x26, 0x86,
+ 0xb9, 0x87, 0x05, 0xdc, 0x62, 0x64, 0x62, 0x3a, 0x53, 0x96,
+ 0x23, 0xc6, 0x6b, 0xf6, 0xb0, 0x7c, 0xca, 0x0c, 0x30, 0x90,
+ 0x85, 0x95, 0xb8, 0x7a, 0x1b, 0xa5, 0x3e, 0xb6, 0x08, 0x80,
+ 0xd5, 0x64, 0xb6, 0x65, 0x8d, 0x6a, 0xf3, 0x5a, 0x70, 0x4a,
+ 0x11, 0x79, 0x8a, 0xe0, 0xac, 0x29, 0x4e, 0x84, 0x1b, 0xdf,
+ 0xcb, 0xf8, 0xc4, 0x12, 0x3b, 0x6a, 0xad, 0x42, 0x07, 0x1a,
+ 0x76, 0x5e, 0xda, 0x5a, 0x23, 0x44, 0x1d, 0x2d, 0xc9, 0xec,
+ 0x07, 0x80, 0xdb, 0x6b, 0xf9, 0xb3, 0x7d, 0x60, 0xb5, 0xa9,
+ 0xe4, 0xac, 0x57, 0x1a, 0x62, 0x8b, 0x2e, 0x07, 0x40, 0x5d,
+ 0x72, 0xa6, 0x55, 0x43, 0xf9, 0xf4, 0xa5, 0xf7, 0xeb, 0x2d,
+ 0xf8, 0xf6, 0xe1, 0x97, 0x6d, 0x8c, 0x3c, 0xef, 0x9d, 0xf8,
+ 0xfc, 0xdf, 0xe2, 0xbd, 0xf4, 0x96, 0x69, 0x31, 0xbe, 0x69,
+ 0xe3, 0x73, 0x12, 0xab, 0x62, 0x8d, 0x8c, 0xbd, 0xf1, 0x25,
+ 0xd8, 0x9c, 0x13, 0x68, 0x9f, 0x31, 0xb5, 0x23, 0xb6, 0x16,
+ 0x1f, 0xea, 0x5d, 0x6e, 0x64, 0xdb, 0x02, 0xd4, 0x3f, 0x26,
+ 0x9a, 0x08, 0x44, 0xa8, 0x05, 0x39, 0x0c, 0xc2, 0x16, 0xe0,
+ 0x15, 0x71, 0xbf, 0xb4, 0x6e, 0x80, 0x61, 0xf2, 0x9b, 0x4f,
+ 0xb4, 0x12, 0x33, 0x4d, 0x6a, 0x86, 0xe2, 0xf0, 0xd9, 0x4d,
+ 0x45, 0x7a, 0x44, 0x4c, 0x3c, 0x6f, 0xa5, 0x31, 0x47, 0xcd,
+ 0x67, 0x0f, 0x43, 0xa4, 0x2e, 0xd6, 0xe6, 0xdd, 0xc2, 0xf0,
+ 0xa8, 0x7e, 0x0f, 0x1f, 0x59, 0x81, 0x3a, 0xde, 0xba, 0xa1,
+ 0x32, 0xe6, 0x98, 0x09, 0x9c, 0xc4, 0x2a, 0xc4, 0x1c, 0xf5,
+ 0x10, 0x31, 0xfb, 0xcf, 0x96, 0x55, 0x0f, 0xec, 0x5d, 0x63,
+ 0x4f, 0xcb, 0x74, 0x27, 0x35, 0xf3, 0x3a, 0x6b, 0x99, 0x30,
+ 0x47, 0xbb, 0xf9, 0x60, 0x08, 0x77, 0x7d, 0xef, 0xd2, 0x8c,
+ 0xbd, 0xa9, 0x39, 0xda, 0xc1, 0x6a, 0xdb, 0x0b, 0x71, 0x94,
+ 0xba, 0xe6, 0xc2, 0x09, 0xd6, 0x67, 0x00, 0xe2, 0x7c, 0x1b,
+ 0x8f, 0x89, 0x09, 0x91, 0xc1, 0xee, 0x51, 0xaf, 0x09, 0x8a,
+ 0x95, 0xed, 0xd9, 0x86, 0x2a, 0xf2, 0xc6, 0xfd, 0x72, 0x2f,
+ 0x3d, 0x18, 0x9f, 0x7f, 0x4a, 0x6a, 0x98, 0x9e, 0x6e, 0x48,
+ 0xe0, 0x69, 0x32, 0x97, 0x80, 0xe9, 0x7a, 0x2b, 0x5a, 0x5c,
+ 0xdb, 0xcb, 0x58, 0xf3, 0x8b, 0x7f, 0x2c, 0x05, 0x9f, 0xa6,
+ 0xbf, 0x98, 0xc8, 0x59, 0x20, 0x0e, 0x6c, 0xab, 0x99, 0x76,
+ 0xc8, 0xa2, 0xe9, 0x78, 0x31, 0xe8, 0xc1, 0x1f, 0xda, 0x9d,
+ 0x6a, 0xd3, 0x96, 0xbd, 0xbf, 0xda, 0x52, 0x2a, 0x3e, 0x48,
+ 0x2f, 0xd0, 0x1f, 0xac, 0x85, 0xf9, 0x9c, 0xa7, 0x17, 0xa9,
+ 0x25, 0x75, 0xb0, 0x54, 0x1c, 0x15, 0xc7, 0x17, 0x29, 0x79,
+ 0xb9, 0x90, 0x6a, 0x4d, 0x3a, 0x08, 0x26, 0x50, 0x30, 0xa7,
+ 0x41, 0x56, 0x9f, 0x0b, 0xb2, 0x6a, 0xfa, 0x1b, 0xa9, 0xfc,
+ 0xab, 0x50, 0x48, 0x85, 0x71, 0x0d, 0x68, 0x55, 0x91, 0x96,
+ 0xa2, 0x87, 0x51, 0xda, 0xf8, 0x4c, 0xfb, 0x63, 0x6a, 0x5f,
+ 0xa9, 0xcb, 0xf0, 0x12, 0xa5, 0x07, 0xb8, 0xe0, 0x95, 0xb7,
+ 0x81, 0x2a, 0x47, 0x1e, 0x34, 0x2c, 0xb5, 0xd0, 0x27, 0x84,
+ 0xee, 0x33, 0xd1, 0xc4, 0x73, 0xad, 0xc3, 0x05, 0xaa, 0x18,
+ 0x87, 0xb4, 0x96, 0x0c, 0x5f, 0xe2, 0xd0, 0x30, 0x00, 0xc7,
+ 0x22, 0x80, 0xe3, 0xd4, 0xf2, 0xd5, 0x55, 0x72, 0x59, 0x61,
+ 0x3f, 0xea, 0x4a, 0x5d, 0xf7, 0xb2, 0x44, 0x16, 0x96, 0x35,
+ 0x73, 0x33, 0x77, 0xee, 0xeb, 0x65, 0xef, 0x41, 0xc2, 0xe0,
+ 0xcc, 0x04, 0x09, 0x90, 0xa0, 0xe3, 0xec, 0x65, 0x8d, 0xe1,
+ 0xca, 0xa7, 0x75, 0xb4, 0x92, 0x98, 0xdd, 0x88, 0xcd, 0x48,
+ 0x0c, 0xf9, 0xb0, 0x45, 0xbd, 0x9f, 0x98, 0x8d, 0x5a, 0x50,
+ 0x47, 0x65, 0x68, 0x6f, 0xc3, 0x08, 0x78, 0x4c, 0xbc, 0xbc,
+ 0x8f, 0x16, 0x48, 0x69, 0x08, 0xf5, 0x03, 0xc5, 0x25, 0x9c,
+ 0x90, 0x3f, 0x54, 0x80, 0x7c, 0x9d, 0x90, 0x26, 0x77, 0x56,
+ 0xca, 0x1c, 0x89, 0xfe, 0xc8, 0xd1, 0x3c, 0x1b, 0xc4, 0x20,
+ 0x64, 0xc8, 0x61, 0x09, 0x1e, 0x36, 0x21, 0xcd, 0x94, 0xb3,
+ 0x03, 0xd9, 0xbc, 0x30, 0x37, 0xc6, 0xff, 0x98, 0xe2, 0x7b,
+ 0xc4, 0x02, 0xcf, 0x48, 0x78, 0x5c, 0xdd, 0xf8, 0x1b, 0x7b,
+ 0x1e, 0x21, 0xaf, 0x2a, 0x1b, 0xe3, 0x2c, 0x1a, 0x35, 0x33,
+ 0x8f, 0xff, 0xac, 0x41, 0x3d, 0x41, 0x81, 0x40, 0x13, 0x75,
+ 0xfc, 0x44, 0x9e, 0x57, 0xe5, 0x7d, 0x4c, 0x3a, 0x61, 0xa4,
+ 0x17, 0xcf, 0x28, 0x1a, 0x90, 0xc2, 0x56, 0xa9, 0x62, 0x03,
+ 0xd1, 0xbd, 0x77, 0x54, 0x70, 0x6a, 0x96, 0xce, 0xbd, 0xe0,
+ 0x86, 0x2f, 0x2c, 0x1b, 0x2c, 0x32, 0x94, 0xd3, 0x81, 0x9b,
+ 0x45, 0xb9, 0x90, 0x1f, 0xf7, 0xca, 0xb4, 0xe6, 0xcc, 0x29,
+ 0x12, 0x56, 0x8c, 0x2d, 0xd1, 0x68, 0x63, 0xf1, 0x6f, 0x39,
+ 0x16, 0xe9, 0x24, 0x55, 0xcd, 0x3d, 0xd7, 0x31, 0xa2, 0x46,
+ 0xee, 0x9c, 0xa2, 0x73, 0x8c, 0x75, 0x5c, 0xc5, 0x48, 0x12,
+ 0xd7, 0x18, 0x7e, 0xea, 0x05, 0x23, 0x11, 0x55, 0x1c, 0x07,
+ 0xdd, 0xa3, 0xca, 0x4b, 0x84, 0x4c, 0xf4, 0xf4, 0xc4, 0xd3,
+ 0xb1, 0xc2, 0x33, 0xb0, 0x9c, 0x85, 0x86, 0xb8, 0x80, 0xd6,
+ 0xe3, 0x76, 0xba, 0x38, 0x84, 0xca, 0xa8, 0x1e, 0x30, 0x48,
+ 0xf3, 0x8b, 0x37, 0xf1, 0x16, 0x78, 0x57, 0xcb, 0xf4, 0x4d,
+ 0x2c, 0x8f, 0xb8, 0x2c, 0xae, 0x4e, 0x31, 0x4d, 0xee, 0xb9,
+ 0x50, 0x31, 0xe4, 0x57, 0xd4, 0xaf, 0x3f, 0xf0, 0x8d, 0xdb,
+ 0xe6, 0x4b, 0x11, 0x68, 0x72, 0x81, 0xb8, 0xe3, 0x36, 0x19,
+ 0x96, 0xaf, 0x4f, 0x3d, 0x8a, 0x3d, 0x80, 0x5b, 0x68, 0xdb,
+ 0xb7, 0x3f, 0x97, 0x43, 0xca, 0x7d, 0x3c, 0x26, 0x81, 0x03,
+ 0xe1, 0xa9, 0x18, 0x79, 0x91, 0xbd, 0x0f, 0x01, 0x2a, 0x0e,
+ 0xbf, 0x92, 0x83, 0x51, 0x69, 0x08, 0x07, 0x82, 0x3b, 0x02,
+ 0x8c, 0x8c, 0x37, 0xd9, 0xfb, 0x43, 0xfe, 0x5c, 0x64, 0xe5,
+ 0x5a, 0x23, 0xe3, 0xec, 0x08, 0xa1, 0x82, 0x6a, 0x0b, 0x9f,
+ 0x7b, 0x78, 0x06, 0xad, 0xa0, 0xa7, 0x49, 0x6b, 0x67, 0x62,
+ 0x93, 0x0e, 0x17, 0xa3, 0x35, 0x22, 0x0f, 0x1b, 0x75, 0x30,
+ 0xff, 0xb1, 0xfc, 0xef, 0x2e, 0x05, 0x08, 0x28, 0x3d, 0x44,
+ 0x8a, 0x4a, 0x03, 0xe3, 0xde, 0xde, 0x87, 0x1a, 0xf9, 0xd9,
+ 0x14, 0x82, 0x4d, 0xe4, 0xe1, 0x96, 0xd2, 0x24, 0x22, 0x71,
+ 0xb3, 0x8d, 0x61, 0xae, 0xbc, 0x25, 0xc9, 0x56, 0xa7, 0xc7,
+ 0xaa, 0xa6, 0xdb, 0xfb, 0x53, 0x74, 0xa5, 0xe8, 0x24, 0x28,
+ 0xac, 0x5d, 0x50, 0x1d, 0x1d, 0x52, 0xa5, 0x25, 0xda, 0x6b,
+ 0xee, 0x4b, 0xba, 0x7d, 0x57, 0x62, 0xe0, 0xaa, 0xd1, 0xa6,
+ 0xe8, 0x93, 0x69, 0x9e, 0x69, 0x48, 0xa9, 0xf2, 0xc1, 0x43,
+ 0xfd, 0x80, 0x1a, 0x77, 0x5a, 0xca, 0x31, 0xb1, 0xc6, 0x8c,
+ 0x2f, 0xb0, 0xb7, 0x04, 0x38, 0x83, 0x76, 0xfc, 0x5f, 0x6b,
+ 0x62, 0xb2, 0xa4, 0x79, 0xc6, 0x61, 0x82, 0x48, 0xfc, 0x1b,
+ 0x54, 0x69, 0x13, 0xd5, 0x9b, 0x68, 0x12, 0x5b, 0x4a, 0xcf,
+ 0xa2, 0xb9, 0x50, 0x9f, 0xc0, 0xcc, 0xe6, 0x18, 0xc8, 0x04,
+ 0xaf, 0xc0, 0x5b, 0x1d, 0xa5, 0xa7, 0x4c, 0xbe, 0x79, 0xc7,
+ 0x51, 0xce, 0xbb, 0xfd, 0x2f, 0x65, 0x3e, 0x34, 0x69, 0xed,
+ 0x8d, 0xc6, 0x09, 0x62, 0x33, 0x12, 0x58, 0xf2, 0x4f, 0x96,
+ 0x8e, 0xb2, 0x93, 0xf8, 0xbf, 0xf8, 0xeb, 0x83, 0x4c, 0x3b,
+ 0xc1, 0x26, 0x4c, 0x45, 0x1f, 0x6e, 0x04, 0xe3, 0xcf, 0x13,
+ 0x3b, 0x34, 0xb4, 0x36, 0x48, 0x4a, 0xd2, 0x3d, 0x8a, 0xd6,
+ 0x57, 0x98, 0x74, 0x17, 0xba, 0x14, 0x3a, 0x58, 0x99, 0x68,
+ 0xad, 0x2b, 0x81, 0x6f, 0x57, 0x63, 0x52, 0x19, 0x36, 0xfe,
+ 0x1d, 0xcb, 0x73, 0xa1, 0x35, 0xb2, 0xf1, 0xf1, 0x26, 0x9e,
+ 0x2b, 0xf3, 0x1b, 0xcd, 0x10, 0xd7, 0x92, 0x29, 0x65, 0x1e,
+ 0x02, 0x8d, 0x08, 0x4e, 0x68, 0xd3, 0x70, 0xf7, 0x87, 0xe5,
+ 0x53, 0x04, 0xa4, 0x43, 0xc5, 0x30, 0x74, 0x4c, 0x5e, 0xe1,
+ 0xf6, 0xa8, 0xd8, 0x55, 0x95, 0xc8, 0x42, 0xd8, 0xfc, 0x3d,
+ 0x2e, 0x8d, 0x8f, 0x0a, 0x20, 0x76, 0xd3, 0x7a, 0xfb, 0x07,
+ 0xba, 0xd8, 0x07, 0xef, 0x29, 0x3a, 0x3d, 0x81, 0x90, 0xaf,
+ 0x0c, 0x13, 0x96, 0x72, 0xb6, 0x3c, 0x0e, 0x57, 0x96, 0x3e,
+ 0x51, 0x91, 0xdb, 0xb0, 0x2d, 0xdf, 0x31, 0x39, 0x21, 0x29,
+ 0xbb, 0x17, 0x0a, 0x23, 0x6b, 0xe4, 0xdc, 0x69, 0x27, 0xc4,
+ 0x20, 0x98, 0xfd, 0x34, 0xca, 0x7a, 0x66, 0x20, 0x58, 0xd2,
+ 0x36, 0x7f, 0x2b, 0xa7, 0xd1, 0xde, 0x6f, 0x36, 0xb4, 0xf2,
+ 0x3b, 0x20, 0x5d, 0x02
+ };
+
+ if(target < CHIAKI_TARGET_PS4_10)
+ return CHIAKI_ERR_INVALID_DATA;
+
+ const uint8_t *keys_a = chiaki_target_is_ps5(target) ? keys_a_ps5 : keys_a_ps4;
+ const uint8_t *keys_b = chiaki_target_is_ps5(target) ? keys_b_ps5 : keys_b_ps4;
+
+ const uint8_t *key = &keys_a[(nonce[0] >> 3) * 0x70];
+ for(size_t i=0; i> 3) * 0x70];
+ if(chiaki_target_is_ps5(target))
+ {
+ for(size_t i=0; ibright, rpcrypt->ambassador, nonce, morning);
+ static const uint8_t ps4_keys_1[] = {
+ 0xc8, 0x48, 0xc2, 0xb4, 0x08, 0xeb, 0x88, 0xf7, 0x5f, 0x4a,
+ 0x09, 0x2d, 0x59, 0x1f, 0x09, 0xcd, 0x1c, 0x18, 0xf4, 0x7a,
+ 0x28, 0x4a, 0x96, 0x6d, 0xb3, 0x59, 0x71, 0x53, 0x75, 0x7e,
+ 0x82, 0x50, 0x57, 0xe4, 0x59, 0xb3, 0xf4, 0x49, 0x69, 0x40,
+ 0xeb, 0x17, 0xc9, 0x9f, 0x17, 0x97, 0x71, 0xae, 0xc9, 0x60,
+ 0x7f, 0xf8, 0x2e, 0x08, 0x94, 0xe8, 0x43, 0xea, 0xda, 0x6b,
+ 0xe5, 0x19, 0x59, 0x33, 0x1f, 0x89, 0xae, 0x47, 0x57, 0x7b,
+ 0x1c, 0x66, 0xfe, 0xff, 0x95, 0xbf, 0x55, 0x6b, 0xd5, 0x93,
+ 0x27, 0xea, 0xa6, 0x24, 0x67, 0x39, 0x9f, 0xd3, 0x0c, 0xaa,
+ 0x26, 0x42, 0xe7, 0x66, 0x4d, 0xd8, 0x18, 0x75, 0xfe, 0x44,
+ 0x03, 0x46, 0xee, 0x3e, 0xf8, 0x3c, 0xb7, 0x85, 0x97, 0x03,
+ 0x07, 0x06, 0x92, 0xff, 0x59, 0x17, 0x27, 0x0b, 0x21, 0xf7,
+ 0x05, 0x7f, 0x69, 0x90, 0x0e, 0x38, 0x91, 0xc6, 0x67, 0x23,
+ 0x48, 0xba, 0x08, 0x8e, 0x57, 0xdd, 0x91, 0xd0, 0x40, 0x47,
+ 0x1c, 0x5b, 0xbf, 0xc8, 0x06, 0x3f, 0x96, 0xa0, 0xdc, 0x00,
+ 0xe5, 0x9a, 0xf5, 0x3b, 0x90, 0x80, 0x66, 0xbb, 0x0f, 0x93,
+ 0x30, 0x07, 0x2b, 0x56, 0x45, 0xa0, 0x9a, 0xb1, 0xb0, 0x72,
+ 0x0c, 0xe4, 0xdd, 0x70, 0xdd, 0x7c, 0x5a, 0xbf, 0xd4, 0xe8,
+ 0x0d, 0xca, 0x37, 0xe5, 0x0e, 0xfd, 0x12, 0xee, 0x79, 0x9a,
+ 0x5e, 0xa7, 0x1e, 0x31, 0xaf, 0x1f, 0x46, 0x52, 0xca, 0xf3,
+ 0x42, 0x00, 0x3d, 0xf2, 0x89, 0x7c, 0x1c, 0x77, 0x60, 0xb7,
+ 0x4a, 0x80, 0x76, 0x47, 0x4b, 0x3f, 0xb6, 0x91, 0x2f, 0x9c,
+ 0xc2, 0xf1, 0xad, 0x44, 0x29, 0xcb, 0x32, 0x8c, 0x0a, 0x8d,
+ 0x05, 0x75, 0x46, 0xa1, 0xf8, 0xda, 0x1a, 0xa7, 0x20, 0xde,
+ 0x32, 0x59, 0xfe, 0x70, 0xb5, 0x87, 0xf3, 0x92, 0xfd, 0xb4,
+ 0xdf, 0xf4, 0xa6, 0xe3, 0x7d, 0x98, 0x3b, 0xe1, 0xba, 0x18,
+ 0xdb, 0x61, 0xd1, 0xc2, 0xa6, 0xee, 0x08, 0x25, 0xfa, 0x86,
+ 0x8a, 0x7b, 0xfe, 0xbc, 0x02, 0xbd, 0x22, 0x5f, 0x25, 0x30,
+ 0x51, 0x5d, 0x28, 0x36, 0x5a, 0x29, 0x8e, 0x52, 0xeb, 0x49,
+ 0x14, 0x28, 0x7f, 0x0a, 0xc4, 0x69, 0x25, 0x85, 0x6b, 0xec,
+ 0x33, 0x33, 0x57, 0xab, 0x50, 0x13, 0xcf, 0xe7, 0x73, 0x78,
+ 0x23, 0x06, 0x4d, 0x1f, 0xbb, 0x38, 0x11, 0xb1, 0x6e, 0x6c,
+ 0x6f, 0xcd, 0x4b, 0x0e, 0x42, 0x85, 0x80, 0xbb, 0xa8, 0x39,
+ 0x68, 0xc9, 0x5b, 0xbb, 0x46, 0x58, 0x86, 0x88, 0x57, 0x88,
+ 0x5e, 0xea, 0x7c, 0x37, 0xdf, 0xfd, 0x02, 0x39, 0x45, 0x89,
+ 0x2e, 0xf2, 0xe4, 0xf0, 0x08, 0xc0, 0xb6, 0xeb, 0x9c, 0x6e,
+ 0x7a, 0x81, 0xa3, 0x26, 0x46, 0xfe, 0xe1, 0x70, 0x3c, 0x3e,
+ 0x11, 0x7a, 0x32, 0xce, 0x45, 0x02, 0x7d, 0x32, 0xcd, 0x08,
+ 0x07, 0x06, 0xa3, 0xa8, 0xf7, 0x34, 0xfe, 0xee, 0x06, 0xb0,
+ 0x14, 0xd6, 0x6b, 0x2d, 0x2e, 0x01, 0xaf, 0x77, 0x11, 0xec,
+ 0x1f, 0x31, 0x38, 0x17, 0x9c, 0xd0, 0xe0, 0xc5, 0x4d, 0xa4,
+ 0xd6, 0xad, 0xb9, 0xe6, 0xe1, 0xe3, 0xe2, 0x9e, 0x44, 0x91,
+ 0x9a, 0x5e, 0x26, 0xca, 0xcc, 0xda, 0x4d, 0xd7, 0x78, 0x6a,
+ 0x75, 0xa6, 0x19, 0xad, 0xcc, 0x62, 0xc7, 0xb6, 0x0d, 0x14,
+ 0xb1, 0xbe, 0xeb, 0xcb, 0x10, 0xcf, 0xa9, 0xee, 0xe2, 0x42,
+ 0x08, 0x35, 0x8a, 0x5c, 0xbc, 0xf1, 0x49, 0xfe, 0x64, 0x78,
+ 0x03, 0x49, 0x0c, 0x85, 0xf0, 0xe4, 0x77, 0x26, 0xd2, 0x5e,
+ 0xf5, 0xc1, 0x3b, 0x3d, 0x2d, 0xcc, 0xcf, 0x2a, 0xac, 0xed,
+ 0x88, 0x52, 0x74, 0x6d, 0xbf, 0xb2, 0xb9, 0xf7, 0x58, 0x51,
+ 0x1c, 0x50, 0xf6, 0x3d, 0xf2, 0xc4, 0x47, 0x0a, 0x21, 0x30,
+ 0x47, 0x81, 0x2d, 0xe4, 0x75, 0x0d, 0x8f, 0x2d, 0x22, 0xb0,
+ 0x63, 0x27
+ };
+
+ static const uint8_t ps5_keys_1[512] = {
+ 0x79, 0x4d, 0x78, 0x30, 0xfe, 0x10, 0x52, 0x4c, 0xa8, 0x90,
+ 0x5b, 0x9a, 0x7e, 0x5f, 0xd3, 0xe1, 0x13, 0xe0, 0xf1, 0x0f,
+ 0xa3, 0xe7, 0xbb, 0x45, 0x7f, 0xdc, 0x8e, 0xd5, 0xf1, 0x04,
+ 0x5c, 0x78, 0x51, 0xef, 0xf8, 0x65, 0x59, 0x03, 0x39, 0x84,
+ 0x37, 0xae, 0x59, 0xdf, 0x23, 0xb6, 0x60, 0x34, 0xe6, 0x4b,
+ 0xe2, 0xf5, 0x4c, 0x13, 0xc6, 0xda, 0xf9, 0xfd, 0xb3, 0x65,
+ 0x84, 0xd6, 0x45, 0xec, 0x2c, 0x00, 0xf2, 0xed, 0xdc, 0xcb,
+ 0x93, 0x6e, 0x61, 0x46, 0xe5, 0xd6, 0x01, 0x94, 0xee, 0x78,
+ 0x85, 0x0e, 0x68, 0x5e, 0xb5, 0x5b, 0xcd, 0xd3, 0x63, 0x41,
+ 0xfc, 0x81, 0x43, 0x1c, 0x6f, 0x7c, 0xba, 0xe8, 0xbd, 0x86,
+ 0x31, 0xd5, 0x70, 0x7f, 0xb5, 0x4a, 0x90, 0x3e, 0x84, 0xe1,
+ 0x71, 0xe0, 0x02, 0x99, 0xf4, 0x71, 0xe7, 0x02, 0xed, 0x36,
+ 0xaf, 0xde, 0x56, 0xc2, 0x90, 0xe0, 0xae, 0xc2, 0xf9, 0xaf,
+ 0x53, 0xc6, 0xd8, 0x62, 0x16, 0x32, 0x27, 0xfb, 0x6e, 0x9b,
+ 0x48, 0xc6, 0xea, 0xff, 0x6f, 0x78, 0x02, 0x22, 0x98, 0x2c,
+ 0x1f, 0xbf, 0xb0, 0x8e, 0xa9, 0x39, 0xbc, 0xdf, 0x17, 0xde,
+ 0xd7, 0x0e, 0xe1, 0x7a, 0x01, 0x0e, 0xc3, 0x87, 0xfc, 0xaa,
+ 0xe4, 0x6b, 0x0f, 0x5b, 0x0a, 0xf1, 0x18, 0x19, 0x8a, 0xe5,
+ 0x2c, 0x36, 0x9b, 0x40, 0x30, 0x99, 0x24, 0x94, 0x48, 0xd7,
+ 0x47, 0xb2, 0xaf, 0x6b, 0x8c, 0x40, 0x9e, 0x4d, 0x6d, 0x34,
+ 0x07, 0xc1, 0x26, 0x2f, 0xbb, 0x14, 0xf7, 0xbc, 0x36, 0x52,
+ 0xbd, 0x84, 0xfe, 0x4a, 0x9a, 0xf4, 0x8a, 0xdb, 0x34, 0x89,
+ 0xaa, 0xf1, 0x0d, 0x94, 0x0b, 0x92, 0xf4, 0x1c, 0xe4, 0x6c,
+ 0x79, 0x2d, 0x6e, 0xc0, 0x19, 0x0a, 0xd5, 0x55, 0x94, 0x14,
+ 0x05, 0x13, 0xc2, 0x62, 0x23, 0xb3, 0xd4, 0x26, 0xc4, 0x44,
+ 0x56, 0x7a, 0xcd, 0x1c, 0xea, 0xd4, 0x74, 0xb9, 0x36, 0x40,
+ 0x9f, 0x08, 0xfb, 0x49, 0x62, 0x05, 0x92, 0x98, 0xad, 0x1d,
+ 0x9f, 0x8a, 0x76, 0x8b, 0xd4, 0x0f, 0x21, 0x40, 0x76, 0xb6,
+ 0x16, 0x91, 0x45, 0x93, 0x66, 0xcc, 0x12, 0xea, 0x4d, 0xf4,
+ 0x09, 0xe2, 0xac, 0x33, 0xd0, 0x6f, 0x43, 0x51, 0x07, 0x3e,
+ 0xd7, 0x95, 0x2c, 0x1e, 0x1f, 0x0c, 0x24, 0xb3, 0x0e, 0x3a,
+ 0xef, 0x95, 0xf5, 0xeb, 0x77, 0xdd, 0x20, 0xf2, 0x35, 0x98,
+ 0xf2, 0xae, 0xa9, 0x66, 0xe6, 0x13, 0xef, 0x5d, 0x3a, 0x2d,
+ 0x66, 0xed, 0xe2, 0x1e, 0xe9, 0x32, 0x4a, 0x40, 0xbf, 0x37,
+ 0xc6, 0x70, 0x29, 0xd9, 0x8c, 0xa1, 0x61, 0x4a, 0x29, 0x3d,
+ 0xc7, 0x55, 0x9c, 0x94, 0x9e, 0xc9, 0x11, 0x45, 0x10, 0x28,
+ 0xa7, 0x27, 0xd1, 0xd3, 0xd0, 0x84, 0x79, 0xc7, 0xa9, 0xb0,
+ 0xf6, 0xaf, 0x45, 0x8c, 0x3c, 0xd4, 0xdf, 0x3b, 0xf7, 0x0d,
+ 0xa2, 0x4f, 0x13, 0x97, 0x78, 0x27, 0xf0, 0x48, 0xc0, 0xa5,
+ 0xab, 0x83, 0x01, 0x05, 0xd0, 0x12, 0xd7, 0x1e, 0x12, 0x3a,
+ 0x4e, 0x98, 0x77, 0xae, 0xba, 0xb1, 0x4e, 0xb5, 0x3b, 0x59,
+ 0xca, 0x6d, 0xa5, 0x11, 0x80, 0x91, 0x9c, 0x07, 0x69, 0x59,
+ 0x5a, 0x53, 0x70, 0x7c, 0x95, 0x97, 0x11, 0x6d, 0x66, 0x8d,
+ 0xa3, 0xbd, 0xbb, 0x2d, 0xb0, 0xbf, 0x9b, 0x10, 0xcb, 0xc7,
+ 0x0f, 0x5b, 0x7e, 0x67, 0xe2, 0xb0, 0x4b, 0xba, 0x10, 0x12,
+ 0xb9, 0xbc, 0x97, 0xfd, 0x48, 0xe4, 0x8a, 0xc1, 0x0f, 0xa1,
+ 0x30, 0x9d, 0x56, 0x20, 0x24, 0x1a, 0x7d, 0x5b, 0xa0, 0xb4,
+ 0xbe, 0x9d, 0x38, 0x4f, 0xb4, 0x56, 0xa8, 0x4d, 0x13, 0x7c,
+ 0x44, 0xe8, 0x84, 0x97, 0xeb, 0x78, 0x2c, 0x52, 0x85, 0xe4,
+ 0xa2, 0xf6, 0xf3, 0xd9, 0x71, 0x9e, 0xee, 0xb8, 0x11, 0x47,
+ 0xfb, 0xa9, 0x1b, 0xc7, 0x40, 0xc6, 0xe1, 0x19, 0x6d, 0x50,
+ 0xa1, 0x2a
+ };
+
+ if(target < CHIAKI_TARGET_PS4_10)
+ return CHIAKI_ERR_INVALID_DATA;
+ const uint8_t *keys_1 = chiaki_target_is_ps5(target) ? ps5_keys_1 : ps4_keys_1;
+ uint8_t wurzelbert = chiaki_target_is_ps5(target) ? -0x2d : 0x29;
+
+ for(size_t i=0; itarget = target;
+ chiaki_rpcrypt_bright_ambassador(target, rpcrypt->bright, rpcrypt->ambassador, nonce, morning);
+}
+
+CHIAKI_EXPORT void chiaki_rpcrypt_init_regist_ps4_pre10(ChiakiRPCrypt *rpcrypt, const uint8_t *ambassador, uint32_t pin)
+{
+ rpcrypt->target = CHIAKI_TARGET_PS4_9; // representative, might not be the actual version
+ static const uint8_t regist_aes_key[CHIAKI_RPCRYPT_KEY_SIZE] =
+ { 0x3f, 0x1c, 0xc4, 0xb6, 0xdc, 0xbb, 0x3e, 0xcc, 0x50, 0xba, 0xed, 0xef, 0x97, 0x34, 0xc7, 0xc9 };
memcpy(rpcrypt->ambassador, ambassador, sizeof(rpcrypt->ambassador));
memcpy(rpcrypt->bright, regist_aes_key, sizeof(rpcrypt->bright));
rpcrypt->bright[0] ^= (uint8_t)((pin >> 0x18) & 0xff);
@@ -77,9 +1714,247 @@ CHIAKI_EXPORT void chiaki_rpcrypt_init_regist(ChiakiRPCrypt *rpcrypt, const uint
rpcrypt->bright[3] ^= (uint8_t)((pin >> 0x00) & 0xff);
}
+CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_init_regist(ChiakiRPCrypt *rpcrypt, ChiakiTarget target, const uint8_t *ambassador, size_t key_0_off, uint32_t pin)
+{
+ static const uint8_t ps4_keys_0[512] = {
+ 0xbe, 0xce, 0x5d, 0xf0, 0xc1, 0x7d, 0xb5, 0xd0, 0xcb, 0x30,
+ 0x13, 0x5d, 0xaa, 0x56, 0x23, 0xfb, 0xc4, 0xbc, 0xf1, 0x8f,
+ 0x38, 0x57, 0xfb, 0xd4, 0xd4, 0x3f, 0x26, 0x38, 0xb5, 0xce,
+ 0xed, 0x6a, 0x21, 0xbc, 0x38, 0xd0, 0x1e, 0x68, 0xcc, 0x7b,
+ 0x45, 0xd1, 0xbe, 0x42, 0x1a, 0x08, 0xaa, 0x16, 0xfd, 0xb0,
+ 0xc0, 0xf4, 0xda, 0x35, 0xe9, 0x12, 0xfd, 0x21, 0x07, 0x48,
+ 0x34, 0xc1, 0xfc, 0x9f, 0x8c, 0xb6, 0xcb, 0x5d, 0xb2, 0x9c,
+ 0x84, 0xe0, 0x1a, 0xfa, 0xa0, 0xc7, 0xeb, 0x3a, 0x93, 0xb3,
+ 0xb3, 0xf1, 0x15, 0xaf, 0x13, 0xbd, 0x21, 0xab, 0xea, 0x5b,
+ 0x80, 0x50, 0x6b, 0x31, 0x1d, 0x7c, 0x1d, 0x40, 0xba, 0x3c,
+ 0x56, 0x0e, 0xe7, 0x94, 0x3a, 0x5b, 0xa1, 0x40, 0x80, 0x74,
+ 0x0a, 0xad, 0x28, 0xcf, 0x47, 0xdf, 0x42, 0xa6, 0x69, 0xe9,
+ 0x5e, 0xbb, 0xc0, 0xc0, 0x0e, 0xb2, 0xc5, 0x8a, 0xee, 0x08,
+ 0x03, 0xd2, 0x84, 0xe5, 0x91, 0x00, 0x1d, 0x46, 0x06, 0x55,
+ 0x09, 0x9d, 0x39, 0x9f, 0xd8, 0xe7, 0xfd, 0xad, 0x9e, 0x93,
+ 0x97, 0xc5, 0xea, 0xe7, 0xa3, 0x10, 0xa7, 0xf2, 0xa2, 0x93,
+ 0x7f, 0x07, 0x04, 0xb4, 0xee, 0xbb, 0xbf, 0x88, 0x23, 0x9c,
+ 0x6e, 0xa7, 0x62, 0xb1, 0x4b, 0x67, 0x1e, 0xb8, 0x3b, 0x1f,
+ 0x64, 0x93, 0x5a, 0x99, 0xec, 0xda, 0xfd, 0x0c, 0x6a, 0xb7,
+ 0xfe, 0xe4, 0x12, 0x76, 0x32, 0x65, 0xb8, 0x41, 0x23, 0xd1,
+ 0x17, 0x09, 0x9c, 0x24, 0x2d, 0x5c, 0x9d, 0x12, 0x79, 0xde,
+ 0xa1, 0xce, 0x69, 0xac, 0xa4, 0xbc, 0x39, 0x2f, 0x57, 0x38,
+ 0x84, 0x61, 0x2d, 0x2a, 0xe8, 0x04, 0xf8, 0xd5, 0x9d, 0x0b,
+ 0xff, 0x7e, 0x56, 0x0c, 0xec, 0x87, 0x0a, 0x1e, 0xab, 0xdf,
+ 0x93, 0x81, 0x13, 0xee, 0xcf, 0x32, 0x02, 0x5a, 0xbf, 0xb0,
+ 0x17, 0xb7, 0xba, 0xb5, 0x7f, 0xf0, 0x01, 0x7b, 0xe1, 0xcb,
+ 0x39, 0x7e, 0x60, 0x6d, 0xa4, 0x75, 0x6e, 0x29, 0x92, 0x45,
+ 0xa6, 0x4f, 0x74, 0x00, 0x86, 0x78, 0x73, 0xbe, 0xfd, 0x3e,
+ 0xe0, 0xd1, 0x0c, 0x6c, 0x0b, 0x49, 0x09, 0x83, 0x6c, 0x85,
+ 0x8a, 0x1d, 0xcb, 0x16, 0xce, 0x81, 0x7c, 0x49, 0xc9, 0x2c,
+ 0x63, 0x61, 0xde, 0xe2, 0x3f, 0x98, 0xb2, 0x73, 0xf0, 0x9a,
+ 0xec, 0x7b, 0x7c, 0xf1, 0xc9, 0xe1, 0x7f, 0xa5, 0x19, 0x8b,
+ 0x4b, 0xe8, 0x38, 0xa4, 0x34, 0x7d, 0xf4, 0x28, 0xfe, 0x0d,
+ 0x4d, 0x11, 0x57, 0x0c, 0x95, 0xf1, 0xaf, 0xd7, 0x34, 0x80,
+ 0xf4, 0xeb, 0x9b, 0x50, 0xe6, 0x6a, 0x5d, 0xea, 0xce, 0x0c,
+ 0x85, 0x4e, 0xc5, 0x5b, 0x93, 0x44, 0xc4, 0x24, 0x98, 0x80,
+ 0xfc, 0xf7, 0x72, 0x9c, 0x31, 0x0b, 0xee, 0x89, 0x67, 0xb3,
+ 0xa2, 0x69, 0x4f, 0xb3, 0x79, 0x5a, 0x14, 0x02, 0x70, 0xed,
+ 0x50, 0x13, 0x75, 0x00, 0x6a, 0xf3, 0xc6, 0x05, 0x1a, 0x00,
+ 0x33, 0x34, 0xf5, 0xac, 0x9e, 0x04, 0xdb, 0xc2, 0x00, 0xb0,
+ 0x1b, 0xc4, 0xf3, 0x97, 0x9d, 0x7f, 0xbe, 0xb8, 0x23, 0x8d,
+ 0x99, 0xe7, 0xcb, 0x74, 0x37, 0x4c, 0x57, 0xec, 0xd2, 0x69,
+ 0x49, 0x46, 0x75, 0x74, 0xaf, 0x51, 0x40, 0xa4, 0x11, 0x7b,
+ 0xb3, 0x2f, 0x51, 0xda, 0xe2, 0xef, 0x33, 0x73, 0x12, 0x18,
+ 0x25, 0x39, 0x03, 0x09, 0xca, 0x49, 0xdc, 0x8e, 0xf1, 0x94,
+ 0xd7, 0x80, 0x17, 0x9e, 0x87, 0x46, 0xc1, 0x04, 0x78, 0xd1,
+ 0xe5, 0x3d, 0x25, 0x88, 0xec, 0x72, 0x3a, 0x28, 0x41, 0x68,
+ 0x14, 0x6e, 0x10, 0xe4, 0xc9, 0x57, 0x75, 0x90, 0xfe, 0x22,
+ 0x1a, 0x63, 0x8e, 0xf4, 0xb8, 0x8d, 0x1a, 0x36, 0xfd, 0xb6,
+ 0xcb, 0x72, 0xc2, 0x97, 0x52, 0x9f, 0x91, 0x72, 0x1b, 0x75,
+ 0x57, 0x90, 0x3b, 0xfd, 0x5a, 0x93, 0x8c, 0xdb, 0xfc, 0xa3,
+ 0x03, 0xdf
+ };
+
+ static const uint8_t ps5_keys_0[512] = {
+ 0x24, 0xd8, 0xc2, 0x69, 0x4c, 0x67, 0x78, 0x71, 0xee, 0x31,
+ 0xbd, 0x2b, 0x83, 0xb2, 0x1d, 0x61, 0xc9, 0xa7, 0x8e, 0xed,
+ 0x9a, 0xd3, 0x6a, 0x6b, 0x5c, 0xc8, 0x35, 0x79, 0xa7, 0x24,
+ 0xe2, 0x17, 0x06, 0x60, 0x2e, 0xdf, 0xf4, 0xdb, 0x27, 0x10,
+ 0x55, 0xd9, 0xea, 0x16, 0x4e, 0x90, 0x0c, 0xbf, 0x40, 0x6f,
+ 0x54, 0xa5, 0x31, 0x70, 0x2d, 0x5d, 0x1e, 0x27, 0xdf, 0x37,
+ 0x40, 0xba, 0x9d, 0x5d, 0xff, 0xe1, 0x05, 0x70, 0x80, 0xd4,
+ 0xb7, 0xc2, 0x96, 0x7f, 0x2f, 0x42, 0xeb, 0x5a, 0x08, 0xde,
+ 0xc1, 0xb5, 0x52, 0x15, 0xf6, 0xb5, 0xf2, 0xd9, 0x69, 0xa5,
+ 0xc7, 0xc4, 0x7f, 0x46, 0x64, 0xa4, 0xfd, 0x46, 0x98, 0xa7,
+ 0xe1, 0x2a, 0x8e, 0x6f, 0xaf, 0x65, 0x42, 0x28, 0xb9, 0xc2,
+ 0x6f, 0x3e, 0xe3, 0xe4, 0x4e, 0xe4, 0x5b, 0x9d, 0x60, 0x10,
+ 0xb8, 0x5a, 0xb0, 0x7d, 0x04, 0x0c, 0x4c, 0x24, 0x78, 0xbd,
+ 0xb8, 0xba, 0xdb, 0x8f, 0xe3, 0xa0, 0x75, 0x6d, 0x28, 0xc2,
+ 0x33, 0x5b, 0x32, 0x83, 0xdd, 0x51, 0xb0, 0xa5, 0x8d, 0x09,
+ 0x66, 0xe4, 0x5c, 0xb8, 0x70, 0x0b, 0xe6, 0x82, 0x14, 0xb6,
+ 0xd2, 0xb0, 0xc2, 0xe0, 0x55, 0xf3, 0x84, 0xad, 0x9d, 0x3a,
+ 0xf8, 0x77, 0xf5, 0x9d, 0x9a, 0xa9, 0x7d, 0xf1, 0x45, 0x1b,
+ 0x9b, 0x55, 0x25, 0xd8, 0xc1, 0xff, 0x03, 0xa5, 0x48, 0x0b,
+ 0x1b, 0x19, 0x0c, 0xbd, 0xe0, 0xcd, 0x48, 0xf3, 0x2c, 0x99,
+ 0x19, 0xd6, 0xb8, 0xbb, 0xd6, 0x35, 0x43, 0x6f, 0x71, 0xe3,
+ 0xef, 0x3e, 0x97, 0xb8, 0xe9, 0x40, 0xa8, 0x47, 0xe0, 0xe0,
+ 0x01, 0x16, 0x9d, 0xa7, 0xe5, 0x94, 0x4b, 0x1d, 0xd2, 0x80,
+ 0xa2, 0x7f, 0xf2, 0x98, 0x10, 0x38, 0x0d, 0xb8, 0x56, 0xc3,
+ 0x7a, 0x4b, 0x4c, 0x85, 0xec, 0x2f, 0x23, 0x89, 0xaf, 0xd5,
+ 0xba, 0x9a, 0xad, 0xb0, 0x61, 0x9c, 0x51, 0xb4, 0x6d, 0x02,
+ 0x49, 0x26, 0xa4, 0x34, 0x84, 0x20, 0x35, 0x30, 0x23, 0x0a,
+ 0x47, 0x14, 0x32, 0x1a, 0x96, 0x0e, 0xe8, 0x0f, 0x96, 0x96,
+ 0xd4, 0xba, 0x68, 0x3a, 0x67, 0x15, 0x74, 0xe0, 0xd6, 0x60,
+ 0x4c, 0x68, 0x50, 0x73, 0x14, 0x2f, 0x11, 0x59, 0xac, 0xc8,
+ 0x32, 0xd1, 0xdb, 0x4c, 0x8a, 0x94, 0x75, 0x33, 0x61, 0xd1,
+ 0xd4, 0xfd, 0xaa, 0x6a, 0x61, 0x68, 0xd8, 0xae, 0x31, 0x4f,
+ 0xb8, 0x07, 0x7b, 0x27, 0x0f, 0xf9, 0x0b, 0xb0, 0xc2, 0x64,
+ 0xb3, 0x72, 0xea, 0x8b, 0x87, 0x40, 0x09, 0xb4, 0x82, 0xb4,
+ 0xad, 0x76, 0xf9, 0x36, 0x05, 0x60, 0x89, 0xc8, 0x20, 0xeb,
+ 0xa5, 0xf1, 0x51, 0x0b, 0x27, 0xa7, 0xf0, 0x76, 0x84, 0x96,
+ 0xeb, 0xb1, 0x2e, 0xc2, 0x85, 0x28, 0xbc, 0x48, 0x34, 0xd4,
+ 0x01, 0x8d, 0x5b, 0x25, 0x54, 0xe0, 0xc4, 0x4f, 0xa0, 0xfa,
+ 0x99, 0x8d, 0x6d, 0x7a, 0x64, 0xb1, 0xa9, 0x5d, 0xa4, 0xf9,
+ 0xf5, 0x22, 0xeb, 0x9a, 0xf4, 0xa8, 0x7a, 0x78, 0x4b, 0x7f,
+ 0xe2, 0x8b, 0x04, 0x50, 0x43, 0x7d, 0x26, 0x2d, 0x19, 0x98,
+ 0x38, 0x6a, 0x4f, 0x2d, 0x30, 0x15, 0x2e, 0x4f, 0xcd, 0xb9,
+ 0xce, 0x9e, 0x8d, 0x12, 0xc9, 0xfe, 0x33, 0x8b, 0x84, 0xce,
+ 0x5b, 0x40, 0xe3, 0x7f, 0x72, 0x6d, 0x6c, 0x8a, 0x6a, 0x9e,
+ 0x54, 0xf1, 0xe3, 0x64, 0x5d, 0x6e, 0x7f, 0xac, 0x1a, 0xe7,
+ 0xf7, 0xfa, 0x00, 0x22, 0xed, 0x2b, 0x23, 0xfa, 0x58, 0xc5,
+ 0xeb, 0x44, 0x92, 0x5d, 0xcc, 0xaa, 0x82, 0x9f, 0x23, 0xfb,
+ 0xa6, 0xc9, 0x65, 0x2a, 0xe0, 0x79, 0x12, 0x65, 0x2c, 0x34,
+ 0xc5, 0x23, 0x16, 0xc9, 0xcc, 0x05, 0x30, 0xf3, 0x96, 0x0b,
+ 0x90, 0x67, 0x1a, 0xa7, 0x69, 0x4c, 0x3e, 0x43, 0x24, 0x9d,
+ 0x4e, 0x68, 0xbd, 0x8b, 0x75, 0x6e, 0x9d, 0x07, 0x6f, 0x1a,
+ 0x6a, 0xba
+ };
+
+ if(target < CHIAKI_TARGET_PS4_10)
+ return CHIAKI_ERR_INVALID_DATA;
+ const uint8_t *keys_0 = chiaki_target_is_ps5(target) ? ps5_keys_0 : ps4_keys_0;
+
+ if(key_0_off >= 0x20)
+ return CHIAKI_ERR_INVALID_DATA;
+
+ rpcrypt->target = target;
+ memcpy(rpcrypt->ambassador, ambassador, sizeof(rpcrypt->ambassador));
+
+ for(size_t i=0; ibright[i] = keys_0[i*0x20 + key_0_off];
+ rpcrypt->bright[0xc] ^= (uint8_t)((pin >> 0x18) & 0xff);
+ rpcrypt->bright[0xd] ^= (uint8_t)((pin >> 0x10) & 0xff);
+ rpcrypt->bright[0xe] ^= (uint8_t)((pin >> 0x08) & 0xff);
+ rpcrypt->bright[0xf] ^= (uint8_t)((pin >> 0x00) & 0xff);
+
+ return CHIAKI_ERR_SUCCESS;
+}
+
+#define HMAC_KEY_SIZE 0x10
+static const uint8_t hmac_key_ps5[HMAC_KEY_SIZE] = { 0x46, 0x46, 0x87, 0xb3, 0x49, 0xca, 0x8c, 0xe8, 0x59, 0xc5, 0x27, 0x0f, 0x5d, 0x7a, 0x69, 0xd6 };
+static const uint8_t hmac_key_ps4[HMAC_KEY_SIZE] = { 0x20, 0xd6, 0x6f, 0x59, 0x04, 0xea, 0x7c, 0x14, 0xe5, 0x57, 0xff, 0xc5, 0x2e, 0x48, 0x8a, 0xc8 };
+static const uint8_t hmac_key_ps4_pre10[HMAC_KEY_SIZE] = { 0xac, 0x07, 0x88, 0x83, 0xc8, 0x3a, 0x1f, 0xe8, 0x11, 0x46, 0x3a, 0xf3, 0x9e, 0xe3, 0xe3, 0x77 };
+
+static const uint8_t *rpcrypt_hmac_key(ChiakiRPCrypt *rpcrypt)
+{
+ switch(rpcrypt->target)
+ {
+ case CHIAKI_TARGET_PS5_1:
+ return hmac_key_ps5;
+ case CHIAKI_TARGET_PS4_8:
+ case CHIAKI_TARGET_PS4_9:
+ return hmac_key_ps4_pre10;
+ default:
+ return hmac_key_ps4;
+ }
+}
+
+#ifdef CHIAKI_LIB_ENABLE_MBEDTLS
CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_generate_iv(ChiakiRPCrypt *rpcrypt, uint8_t *iv, uint64_t counter)
{
- uint8_t hmac_key[] = { 0xac, 0x07, 0x88, 0x83, 0xc8, 0x3a, 0x1f, 0xe8, 0x11, 0x46, 0x3a, 0xf3, 0x9e, 0xe3, 0xe3, 0x77 };
+ const uint8_t *hmac_key = rpcrypt_hmac_key(rpcrypt);
+
+ uint8_t buf[CHIAKI_RPCRYPT_KEY_SIZE + 8];
+ memcpy(buf, rpcrypt->ambassador, CHIAKI_RPCRYPT_KEY_SIZE);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 0] = (uint8_t)((counter >> 0x38) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 1] = (uint8_t)((counter >> 0x30) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 2] = (uint8_t)((counter >> 0x28) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 3] = (uint8_t)((counter >> 0x20) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 4] = (uint8_t)((counter >> 0x18) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 5] = (uint8_t)((counter >> 0x10) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 6] = (uint8_t)((counter >> 0x08) & 0xff);
+ buf[CHIAKI_RPCRYPT_KEY_SIZE + 7] = (uint8_t)((counter >> 0x00) & 0xff);
+
+ uint8_t hmac[CHIAKI_RPCRYPT_KEY_SIZE];
+ unsigned int hmac_len = 0;
+
+
+ mbedtls_md_context_t ctx;
+ mbedtls_md_type_t type = MBEDTLS_MD_SHA256;
+
+ mbedtls_md_init(&ctx);
+
+#define GOTO_ERROR(err) do { \
+ if((err) !=0){ \
+ goto error;} \
+ } while(0)
+ // https://tls.mbed.org/module-level-design-hashing
+ GOTO_ERROR(mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(type) , 1));
+ GOTO_ERROR(mbedtls_md_hmac_starts(&ctx, hmac_key, HMAC_KEY_SIZE));
+ GOTO_ERROR(mbedtls_md_hmac_update(&ctx, (const unsigned char *) buf, sizeof(buf)));
+ GOTO_ERROR(mbedtls_md_hmac_finish(&ctx, hmac));
+#undef GOTO_ERROR
+ memcpy(iv, hmac, CHIAKI_RPCRYPT_KEY_SIZE);
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_SUCCESS;
+error:
+ mbedtls_md_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+}
+
+
+static ChiakiErrorCode chiaki_rpcrypt_crypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz, bool encrypt)
+{
+
+#define GOTO_ERROR(err) do { \
+ if((err) !=0){ \
+ goto error;} \
+ } while(0)
+
+ // https://github.com/ARMmbed/mbedtls/blob/development/programs/aes/aescrypt2.c
+ // build aes context
+ mbedtls_aes_context ctx;
+ mbedtls_aes_init(&ctx);
+
+ // initialization vector
+ uint8_t iv[CHIAKI_RPCRYPT_KEY_SIZE];
+ ChiakiErrorCode err = chiaki_rpcrypt_generate_iv(rpcrypt, iv, counter);
+ if(err != CHIAKI_ERR_SUCCESS)
+ return err;
+
+ GOTO_ERROR(mbedtls_aes_setkey_enc(&ctx, rpcrypt->bright, 128));
+ size_t iv_off = 0;
+ if(encrypt)
+ {
+ GOTO_ERROR(mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_ENCRYPT, sz, &iv_off, iv, in, out));
+ }
+ else
+ {
+ // the aes_crypt_cfb128 does not seems to use the setkey_dec
+ // GOTO_ERROR(mbedtls_aes_setkey_dec(&ctx, rpcrypt->bright, 128));
+ GOTO_ERROR(mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_DECRYPT, sz, &iv_off, iv, in, out));
+ }
+
+#undef GOTO_ERROR
+ mbedtls_aes_free(&ctx);
+
+ return CHIAKI_ERR_SUCCESS;
+
+error:
+ mbedtls_aes_free(&ctx);
+ return CHIAKI_ERR_UNKNOWN;
+}
+
+#else
+CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_generate_iv(ChiakiRPCrypt *rpcrypt, uint8_t *iv, uint64_t counter)
+{
+ const uint8_t *hmac_key = rpcrypt_hmac_key(rpcrypt);
uint8_t buf[CHIAKI_RPCRYPT_KEY_SIZE + 8];
memcpy(buf, rpcrypt->ambassador, CHIAKI_RPCRYPT_KEY_SIZE);
@@ -150,6 +2025,7 @@ static ChiakiErrorCode chiaki_rpcrypt_crypt(ChiakiRPCrypt *rpcrypt, uint64_t cou
EVP_CIPHER_CTX_free(ctx);
return CHIAKI_ERR_SUCCESS;
}
+#endif
CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_encrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz)
{
diff --git a/lib/src/senkusha.c b/lib/src/senkusha.c
index e45d39f..fc4e600 100644
--- a/lib/src/senkusha.c
+++ b/lib/src/senkusha.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -31,6 +16,7 @@
#include
#include
#include
+#include
#ifndef _WIN32
#include
@@ -100,6 +86,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_senkusha_init(ChiakiSenkusha *senkusha, Chi
senkusha->ping_tag = 0;
senkusha->pong_time_us = 0;
+ chiaki_key_state_init(&senkusha->takion.key_state);
+
return CHIAKI_ERR_SUCCESS;
error_state_mutex:
@@ -363,7 +351,7 @@ static ChiakiErrorCode senkusha_run_mtu_in_test(ChiakiSenkusha *senkusha, uint32
uint32_t cur = max;
uint32_t request_id = 0;
- while(max > min)
+ while((max - min) > 1)
{
bool success = false;
for(uint32_t attempt=0; attemptstate_failed = false;
senkusha->mtu_id = ++request_id;
- tkproto_SenkushaMtuCommand mtu_cmd;
+ tkproto_SenkushaMtuCommand mtu_cmd = { 0 };
mtu_cmd.id = request_id;
mtu_cmd.mtu_req = cur;
mtu_cmd.num = 1;
@@ -410,14 +398,14 @@ static ChiakiErrorCode senkusha_run_mtu_in_test(ChiakiSenkusha *senkusha, uint32
}
if(success)
- min = cur + 1;
+ min = cur;
else
- max = cur - 1;
+ max = cur;
cur = min + (max - min) / 2;
}
- CHIAKI_LOGI(senkusha->log, "Senkusha determined inbound MTU %u", (unsigned int)max);
- *mtu = max;
+ CHIAKI_LOGI(senkusha->log, "Senkusha determined inbound MTU %u", (unsigned int)min);
+ *mtu = min;
return CHIAKI_ERR_SUCCESS;
}
@@ -481,7 +469,7 @@ static ChiakiErrorCode senkusha_run_mtu_out_test(ChiakiSenkusha *senkusha, uint3
err = CHIAKI_ERR_SUCCESS;
uint32_t cur = mtu_in;
- while(max > min)
+ while((max - min) > 1)
{
bool success = false;
for(uint32_t attempt=0; attemptlog, "Senkusha failed to send ping");
- goto beach;
+ err = CHIAKI_ERR_TIMEOUT;
+ }
+ else
+ {
+ err = chiaki_cond_timedwait_pred(&senkusha->state_cond, &senkusha->state_mutex, timeout_ms, state_finished_cond_check, senkusha);
}
- err = chiaki_cond_timedwait_pred(&senkusha->state_cond, &senkusha->state_mutex, timeout_ms, state_finished_cond_check, senkusha);
assert(err == CHIAKI_ERR_SUCCESS || err == CHIAKI_ERR_TIMEOUT);
if(!senkusha->state_finished)
@@ -549,14 +540,14 @@ static ChiakiErrorCode senkusha_run_mtu_out_test(ChiakiSenkusha *senkusha, uint3
}
if(success)
- min = cur + 1;
+ min = cur;
else
- max = cur - 1;
+ max = cur;
cur = min + (max - min) / 2;
}
- CHIAKI_LOGI(senkusha->log, "Senkusha determined outbound MTU %u", (unsigned int)max);
- *mtu = max;
+ CHIAKI_LOGI(senkusha->log, "Senkusha determined outbound MTU %u", (unsigned int)min);
+ *mtu = min;
CHIAKI_LOGI(senkusha->log, "Senkusha sending final Client MTU Command");
client_mtu_cmd.id = 2;
@@ -654,7 +645,6 @@ static void senkusha_takion_data(ChiakiSenkusha *senkusha, ChiakiTakionMessageDa
senkusha->state_finished = true;
chiaki_cond_signal(&senkusha->state_cond);
}
-
}
chiaki_mutex_unlock(&senkusha->state_mutex);
}
@@ -887,4 +877,3 @@ static ChiakiErrorCode senkusha_send_data_wait_for_ack(ChiakiSenkusha *senkusha,
return err;
}
-
diff --git a/lib/src/session.c b/lib/src/session.c
index db86c70..aa56e19 100644
--- a/lib/src/session.c
+++ b/lib/src/session.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -46,7 +31,7 @@
#define SESSION_EXPECT_TIMEOUT_MS 5000
static void *session_thread_func(void *arg);
-static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersion *server_version_out);
+static ChiakiErrorCode session_thread_request_session(ChiakiSession *session, ChiakiTarget *target_out);
const char *chiaki_rp_application_reason_string(uint32_t reason)
{
@@ -67,30 +52,43 @@ const char *chiaki_rp_application_reason_string(uint32_t reason)
}
}
-const char *chiaki_rp_version_string(ChiakiRpVersion version)
+const char *chiaki_rp_version_string(ChiakiTarget version)
{
switch(version)
{
- case CHIAKI_RP_VERSION_8_0:
+ case CHIAKI_TARGET_PS4_8:
return "8.0";
- case CHIAKI_RP_VERSION_9_0:
+ case CHIAKI_TARGET_PS4_9:
return "9.0";
+ case CHIAKI_TARGET_PS4_10:
+ return "10.0";
+ case CHIAKI_TARGET_PS5_1:
+ return "1.0";
default:
return NULL;
}
}
-CHIAKI_EXPORT ChiakiRpVersion chiaki_rp_version_parse(const char *rp_version_str)
+CHIAKI_EXPORT ChiakiTarget chiaki_rp_version_parse(const char *rp_version_str, bool is_ps5)
{
- if(strcmp(rp_version_str, "8.0") == 0)
- return CHIAKI_RP_VERSION_8_0;
- if(strcmp(rp_version_str, "9.0") == 0)
- return CHIAKI_RP_VERSION_9_0;
- return CHIAKI_RP_VERSION_UNKNOWN;
+ if(is_ps5)
+ {
+ if(!strcmp(rp_version_str, "1.0"))
+ return CHIAKI_TARGET_PS5_1;
+ return CHIAKI_TARGET_PS5_UNKNOWN;
+ }
+ if(!strcmp(rp_version_str, "8.0"))
+ return CHIAKI_TARGET_PS4_8;
+ if(!strcmp(rp_version_str, "9.0"))
+ return CHIAKI_TARGET_PS4_9;
+ if(!strcmp(rp_version_str, "10.0"))
+ return CHIAKI_TARGET_PS4_10;
+ return CHIAKI_TARGET_PS4_UNKNOWN;
}
CHIAKI_EXPORT void chiaki_connect_video_profile_preset(ChiakiConnectVideoProfile *profile, ChiakiVideoResolutionPreset resolution, ChiakiVideoFPSPreset fps)
{
+ profile->codec = CHIAKI_CODEC_H264;
switch(resolution)
{
case CHIAKI_VIDEO_RESOLUTION_PRESET_360p:
@@ -111,7 +109,7 @@ CHIAKI_EXPORT void chiaki_connect_video_profile_preset(ChiakiConnectVideoProfile
case CHIAKI_VIDEO_RESOLUTION_PRESET_1080p:
profile->width = 1920;
profile->height = 1080;
- profile->bitrate = 10000; // TODO
+ profile->bitrate = 15000;
break;
default:
profile->width = 0;
@@ -160,21 +158,21 @@ CHIAKI_EXPORT const char *chiaki_quit_reason_string(ChiakiQuitReason reason)
return "Unknown Error in Stream Connection";
case CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED:
return "Remote has disconnected from Stream Connection";
+ case CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN:
+ return "Remote has disconnected from Stream Connection the because Server shut down";
case CHIAKI_QUIT_REASON_NONE:
default:
return "Unknown";
}
}
-
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_session_init(ChiakiSession *session, ChiakiConnectInfo *connect_info, ChiakiLog *log)
{
memset(session, 0, sizeof(ChiakiSession));
session->log = log;
session->quit_reason = CHIAKI_QUIT_REASON_NONE;
- session->rp_version = CHIAKI_RP_VERSION_9_0;
+ session->target = connect_info->ps5 ? CHIAKI_TARGET_PS5_1 : CHIAKI_TARGET_PS4_10;
ChiakiErrorCode err = chiaki_cond_init(&session->state_cond);
if(err != CHIAKI_ERR_SUCCESS)
@@ -195,11 +193,18 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_init(ChiakiSession *session, Chiaki
session->login_pin = NULL;
session->login_pin_size = 0;
+ err = chiaki_ctrl_init(&session->ctrl, session);
+ if(err != CHIAKI_ERR_SUCCESS)
+ {
+ CHIAKI_LOGE(session->log, "Ctrl init failed");
+ goto error_stop_pipe;
+ }
+
err = chiaki_stream_connection_init(&session->stream_connection, session);
if(err != CHIAKI_ERR_SUCCESS)
{
CHIAKI_LOGE(session->log, "StreamConnection init failed");
- goto error_stop_pipe;
+ goto error_ctrl;
}
int r = getaddrinfo(connect_info->host, NULL, NULL, &session->connect_info.host_addrinfos);
@@ -211,6 +216,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_init(ChiakiSession *session, Chiaki
chiaki_controller_state_set_idle(&session->controller_state);
+ session->connect_info.ps5 = connect_info->ps5;
memcpy(session->connect_info.regist_key, connect_info->regist_key, sizeof(session->connect_info.regist_key));
memcpy(session->connect_info.morning, connect_info->morning, sizeof(session->connect_info.morning));
@@ -221,10 +227,15 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_init(ChiakiSession *session, Chiaki
memcpy(session->connect_info.did + sizeof(session->connect_info.did) - sizeof(did_suffix), did_suffix, sizeof(did_suffix));
session->connect_info.video_profile = connect_info->video_profile;
+ session->connect_info.video_profile_auto_downgrade = connect_info->video_profile_auto_downgrade;
+ session->connect_info.enable_keyboard = connect_info->enable_keyboard;
+ session->connect_info.enable_dualsense = connect_info->enable_dualsense;
return CHIAKI_ERR_SUCCESS;
error_stop_pipe:
chiaki_stop_pipe_fini(&session->stop_pipe);
+error_ctrl:
+ chiaki_ctrl_fini(&session->ctrl);
error_state_mutex:
chiaki_mutex_fini(&session->state_mutex);
error_state_cond:
@@ -240,6 +251,7 @@ CHIAKI_EXPORT void chiaki_session_fini(ChiakiSession *session)
free(session->login_pin);
free(session->quit_reason_str);
chiaki_stream_connection_fini(&session->stream_connection);
+ chiaki_ctrl_fini(&session->ctrl);
chiaki_stop_pipe_fini(&session->stop_pipe);
chiaki_cond_fini(&session->state_cond);
chiaki_mutex_fini(&session->state_mutex);
@@ -337,20 +349,11 @@ static bool session_check_state_pred_pin(void *user)
|| session->login_pin_entered;
}
-static bool session_check_state_pred_session_id(void *user)
-{
- ChiakiSession *session = user;
- return session->should_stop
- || session->ctrl_failed
- || session->ctrl_session_id_received;
-}
-
#define ENABLE_SENKUSHA
static void *session_thread_func(void *arg)
{
ChiakiSession *session = arg;
- bool success;
chiaki_mutex_lock(&session->state_mutex);
@@ -367,31 +370,42 @@ static void *session_thread_func(void *arg)
CHECK_STOP(quit);
- CHIAKI_LOGI(session->log, "Starting session request");
+ CHIAKI_LOGI(session->log, "Starting session request for %s", session->connect_info.ps5 ? "PS5" : "PS4");
- ChiakiRpVersion server_rp_version = CHIAKI_RP_VERSION_UNKNOWN;
- success = session_thread_request_session(session, &server_rp_version);
+ ChiakiTarget server_target = CHIAKI_TARGET_PS4_UNKNOWN;
+ ChiakiErrorCode err = session_thread_request_session(session, &server_target);
- if(!success && server_rp_version != CHIAKI_RP_VERSION_UNKNOWN)
+ if(err == CHIAKI_ERR_VERSION_MISMATCH && !chiaki_target_is_unknown(server_target))
{
CHIAKI_LOGI(session->log, "Attempting to re-request session with Server's RP-Version");
- session->rp_version = server_rp_version;
- success = session_thread_request_session(session, NULL);
+ session->target = server_target;
+ err = session_thread_request_session(session, &server_target);
}
+ else if(err != CHIAKI_ERR_SUCCESS)
+ QUIT(quit);
- if(!success)
+ if(err == CHIAKI_ERR_VERSION_MISMATCH && !chiaki_target_is_unknown(server_target))
+ {
+ CHIAKI_LOGI(session->log, "Attempting to re-request session even harder with Server's RP-Version!!!");
+ session->target = server_target;
+ err = session_thread_request_session(session, NULL);
+ }
+ else if(err != CHIAKI_ERR_SUCCESS)
+ QUIT(quit);
+
+ if(err != CHIAKI_ERR_SUCCESS)
QUIT(quit);
CHIAKI_LOGI(session->log, "Session request successful");
- chiaki_rpcrypt_init_auth(&session->rpcrypt, session->nonce, session->connect_info.morning);
+ chiaki_rpcrypt_init_auth(&session->rpcrypt, session->target, session->nonce, session->connect_info.morning);
// PS4 doesn't always react right away, sleep a bit
chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, 10, session_check_state_pred, session);
CHIAKI_LOGI(session->log, "Starting ctrl");
- ChiakiErrorCode err = chiaki_ctrl_start(&session->ctrl, session);
+ err = chiaki_ctrl_start(&session->ctrl);
if(err != CHIAKI_ERR_SUCCESS)
QUIT(quit);
@@ -487,27 +501,16 @@ ctrl_failed:
QUIT(quit_ctrl);
}
- session->audio_receiver = chiaki_audio_receiver_new(session);
- if(!session->audio_receiver)
- {
- CHIAKI_LOGE(session->log, "Session failed to initialize Audio Receiver");
- QUIT(quit_ecdh);
- }
-
- session->video_receiver = chiaki_video_receiver_new(session);
- if(!session->video_receiver)
- {
- CHIAKI_LOGE(session->log, "Session failed to initialize Video Receiver");
- QUIT(quit_audio_receiver);
- }
-
chiaki_mutex_unlock(&session->state_mutex);
err = chiaki_stream_connection_run(&session->stream_connection);
chiaki_mutex_lock(&session->state_mutex);
if(err == CHIAKI_ERR_DISCONNECTED)
{
CHIAKI_LOGE(session->log, "Remote disconnected from StreamConnection");
- session->quit_reason = CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED;
+ if(!strcmp(session->stream_connection.remote_disconnect_reason, "Server shutting down"))
+ session->quit_reason = CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_SHUTDOWN;
+ else
+ session->quit_reason = CHIAKI_QUIT_REASON_STREAM_CONNECTION_REMOTE_DISCONNECTED;
session->quit_reason_str = strdup(session->stream_connection.remote_disconnect_reason);
}
else if(err != CHIAKI_ERR_SUCCESS && err != CHIAKI_ERR_CANCELED)
@@ -521,16 +524,7 @@ ctrl_failed:
session->quit_reason = CHIAKI_QUIT_REASON_STOPPED;
}
- chiaki_video_receiver_free(session->video_receiver);
- session->video_receiver = NULL;
-
chiaki_mutex_unlock(&session->state_mutex);
-
-quit_audio_receiver:
- chiaki_audio_receiver_free(session->audio_receiver);
- session->audio_receiver = NULL;
-
-quit_ecdh:
chiaki_ecdh_fini(&session->ecdh);
quit_ctrl:
@@ -552,10 +546,8 @@ quit:
#undef QUIT
}
-
-
-
-typedef struct session_response_t {
+typedef struct session_response_t
+{
uint32_t error_code;
const char *nonce;
const char *rp_version;
@@ -584,9 +576,9 @@ static void parse_session_response(SessionResponse *response, ChiakiHttpResponse
/**
- * @param server_version_out if NULL, version mismatch means to fail the entire session, otherwise report the version here
+ * @param target_out if NULL, version mismatch means to fail the entire session, otherwise report the target here
*/
-static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersion *server_version_out)
+static ChiakiErrorCode session_thread_request_session(ChiakiSession *session, ChiakiTarget *target_out)
{
chiaki_socket_t session_sock = CHIAKI_INVALID_SOCKET;
for(struct addrinfo *ai=session->connect_info.host_addrinfos; ai; ai=ai->ai_next)
@@ -608,11 +600,11 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
set_port(sa, htons(SESSION_PORT));
// TODO: this can block, make cancelable somehow
- int r = getnameinfo(sa, (socklen_t)ai->ai_addrlen, session->connect_info.hostname, sizeof(session->connect_info.hostname), NULL, 0, 0);
+ int r = getnameinfo(sa, (socklen_t)ai->ai_addrlen, session->connect_info.hostname, sizeof(session->connect_info.hostname), NULL, 0, NI_NUMERICHOST);
if(r != 0)
{
- free(sa);
- continue;
+ CHIAKI_LOGE(session->log, "getnameinfo failed with %s, filling the hostname with fallback", gai_strerror(r));
+ memcpy(session->connect_info.hostname, "unknown", 8);
}
CHIAKI_LOGI(session->log, "Trying to request session from %s:%d", session->connect_info.hostname, SESSION_PORT);
@@ -623,7 +615,7 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
#ifdef _WIN32
CHIAKI_LOGE(session->log, "Failed to create socket to request session");
#else
- CHIAKI_LOGE(session->log, "Failed to create socket to request session: %s", strerror(errno));
+ CHIAKI_LOGE(session->log, "Failed to create socket to request session: %s", strerror(errno));
#endif
free(sa);
continue;
@@ -664,19 +656,18 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
break;
}
-
if(CHIAKI_SOCKET_IS_INVALID(session_sock))
{
CHIAKI_LOGE(session->log, "Session request connect failed eventually.");
if(session->quit_reason == CHIAKI_QUIT_REASON_NONE)
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
- return false;
+ return CHIAKI_ERR_NETWORK;
}
CHIAKI_LOGI(session->log, "Connected to %s:%d", session->connect_info.hostname, SESSION_PORT);
static const char session_request_fmt[] =
- "GET /sce/rp/session HTTP/1.1\r\n"
+ "GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"User-Agent: remoteplay Windows\r\n"
"Connection: close\r\n"
@@ -684,6 +675,13 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
"RP-Registkey: %s\r\n"
"Rp-Version: %s\r\n"
"\r\n";
+ const char *path;
+ if(session->target == CHIAKI_TARGET_PS4_8 || session->target == CHIAKI_TARGET_PS4_9)
+ path = "/sce/rp/session";
+ else if(chiaki_target_is_ps5(session->target))
+ path = "/sie/ps5/rp/sess/init";
+ else
+ path = "/sie/ps4/rp/sess/init";
size_t regist_key_len = sizeof(session->connect_info.regist_key);
for(size_t i=0; iquit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
- return false;
+ return CHIAKI_ERR_UNKNOWN;
}
- const char *rp_version_str = chiaki_rp_version_string(session->rp_version);
+ const char *rp_version_str = chiaki_rp_version_string(session->target);
+ if(!rp_version_str)
+ {
+ CHIAKI_LOGE(session->log, "Failed to get version for target, probably invalid target value");
+ session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
+ return CHIAKI_ERR_INVALID_DATA;
+ }
char buf[512];
int request_len = snprintf(buf, sizeof(buf), session_request_fmt,
- session->connect_info.hostname, SESSION_PORT, regist_key_hex, rp_version_str ? rp_version_str : "");
+ path, session->connect_info.hostname, SESSION_PORT, regist_key_hex, rp_version_str ? rp_version_str : "");
if(request_len < 0 || request_len >= sizeof(buf))
{
CHIAKI_SOCKET_CLOSE(session_sock);
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
- return false;
+ return CHIAKI_ERR_UNKNOWN;
}
CHIAKI_LOGI(session->log, "Sending session request");
@@ -724,7 +728,7 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
CHIAKI_LOGE(session->log, "Failed to send session request");
CHIAKI_SOCKET_CLOSE(session_sock);
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
- return false;
+ return CHIAKI_ERR_NETWORK;
}
size_t header_size;
@@ -745,7 +749,7 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
}
CHIAKI_SOCKET_CLOSE(session_sock);
- return false;
+ return CHIAKI_ERR_NETWORK;
}
ChiakiHttpResponse http_response;
@@ -757,12 +761,13 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
CHIAKI_LOGE(session->log, "Failed to parse session request response");
CHIAKI_SOCKET_CLOSE(session_sock);
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
- return false;
+ return CHIAKI_ERR_NETWORK;
}
SessionResponse response;
parse_session_response(&response, &http_response);
+ ChiakiErrorCode r = CHIAKI_ERR_UNKNOWN;
if(response.success)
{
size_t nonce_len = CHIAKI_RPCRYPT_KEY_SIZE;
@@ -773,18 +778,29 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
response.success = false;
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
}
+ else
+ r = CHIAKI_ERR_SUCCESS;
}
- else if(response.error_code == CHIAKI_RP_APPLICATION_REASON_RP_VERSION && server_version_out && response.rp_version)
+ else if((response.error_code == CHIAKI_RP_APPLICATION_REASON_RP_VERSION
+ || response.error_code == CHIAKI_RP_APPLICATION_REASON_UNKNOWN)
+ && target_out && response.rp_version && strcmp(rp_version_str, response.rp_version))
{
- CHIAKI_LOGI(session->log, "Reported RP-Version mismatch. ours = %s, server = %s", rp_version_str ? rp_version_str : "", response.rp_version);
- *server_version_out = chiaki_rp_version_parse(response.rp_version);
- if(*server_version_out != CHIAKI_RP_VERSION_UNKNOWN)
- CHIAKI_LOGI(session->log, "Detected Server RP-Version %s", chiaki_rp_version_string(*server_version_out));
+ CHIAKI_LOGI(session->log, "Reported RP-Version mismatch. ours = %s, server = %s",
+ rp_version_str ? rp_version_str : "", response.rp_version);
+ *target_out = chiaki_rp_version_parse(response.rp_version, session->connect_info.ps5);
+ if(!chiaki_target_is_unknown(*target_out))
+ CHIAKI_LOGI(session->log, "Detected Server RP-Version %s", chiaki_rp_version_string(*target_out));
+ else if(!strcmp(response.rp_version, "5.0"))
+ {
+ CHIAKI_LOGI(session->log, "Reported Server RP-Version is 5.0. This is probably nonsense, let's try with 9.0");
+ *target_out = CHIAKI_TARGET_PS4_9;
+ }
else
{
CHIAKI_LOGE(session->log, "Server RP-Version is unknown");
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_RP_VERSION_MISMATCH;
}
+ r = CHIAKI_ERR_VERSION_MISMATCH;
}
else
{
@@ -799,6 +815,7 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
break;
case CHIAKI_RP_APPLICATION_REASON_RP_VERSION:
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_RP_VERSION_MISMATCH;
+ r = CHIAKI_ERR_VERSION_MISMATCH;
break;
default:
session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_UNKNOWN;
@@ -808,5 +825,25 @@ static bool session_thread_request_session(ChiakiSession *session, ChiakiRpVersi
chiaki_http_response_fini(&http_response);
CHIAKI_SOCKET_CLOSE(session_sock);
- return response.success;
+ return r;
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_goto_bed(ChiakiSession *session)
+{
+ return chiaki_ctrl_goto_bed(&session->ctrl);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_keyboard_set_text(ChiakiSession *session, const char *text)
+{
+ return chiaki_ctrl_keyboard_set_text(&session->ctrl, text);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_keyboard_reject(ChiakiSession *session)
+{
+ return chiaki_ctrl_keyboard_reject(&session->ctrl);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_session_keyboard_accept(ChiakiSession *session)
+{
+ return chiaki_ctrl_keyboard_accept(&session->ctrl);
}
diff --git a/lib/src/sock.c b/lib/src/sock.c
index f8d224e..b126bc2 100644
--- a/lib/src/sock.c
+++ b/lib/src/sock.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
diff --git a/lib/src/stoppipe.c b/lib/src/stoppipe.c
index cb90309..003ad5d 100644
--- a/lib/src/stoppipe.c
+++ b/lib/src/stoppipe.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -34,11 +19,35 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_init(ChiakiStopPipe *stop_pipe)
stop_pipe->event = WSACreateEvent();
if(stop_pipe->event == WSA_INVALID_EVENT)
return CHIAKI_ERR_UNKNOWN;
+#elif defined(__SWITCH__)
+ // currently pipe or socketpare are not available on switch
+ // use a custom udp socket as pipe
+
+ // struct sockaddr_in addr;
+ int addr_size = sizeof(stop_pipe->addr);
+
+ stop_pipe->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if(stop_pipe->fd < 0)
+ return CHIAKI_ERR_UNKNOWN;
+ stop_pipe->addr.sin_family = AF_INET;
+ // bind to localhost
+ stop_pipe->addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ // use a random port (dedicate one socket per object)
+ stop_pipe->addr.sin_port = htons(0);
+ // bind on localhost
+ bind(stop_pipe->fd, (struct sockaddr *) &stop_pipe->addr, addr_size);
+ // listen
+ getsockname(stop_pipe->fd, (struct sockaddr *) &stop_pipe->addr, &addr_size);
+ int r = fcntl(stop_pipe->fd, F_SETFL, O_NONBLOCK);
+ if(r == -1)
+ {
+ close(stop_pipe->fd);
+ return CHIAKI_ERR_UNKNOWN;
+ }
#else
int r = pipe(stop_pipe->fds);
if(r < 0)
return CHIAKI_ERR_UNKNOWN;
-
r = fcntl(stop_pipe->fds[0], F_SETFL, O_NONBLOCK);
if(r == -1)
{
@@ -47,7 +56,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_init(ChiakiStopPipe *stop_pipe)
return CHIAKI_ERR_UNKNOWN;
}
#endif
-
return CHIAKI_ERR_SUCCESS;
}
@@ -55,6 +63,8 @@ CHIAKI_EXPORT void chiaki_stop_pipe_fini(ChiakiStopPipe *stop_pipe)
{
#ifdef _WIN32
WSACloseEvent(stop_pipe->event);
+#elif defined(__SWITCH__)
+ close(stop_pipe->fd);
#else
close(stop_pipe->fds[0]);
close(stop_pipe->fds[1]);
@@ -65,6 +75,10 @@ CHIAKI_EXPORT void chiaki_stop_pipe_stop(ChiakiStopPipe *stop_pipe)
{
#ifdef _WIN32
WSASetEvent(stop_pipe->event);
+#elif defined(__SWITCH__)
+ // send to local socket (FIXME MSG_CONFIRM)
+ sendto(stop_pipe->fd, "\x00", 1, 0,
+ (struct sockaddr*)&stop_pipe->addr, sizeof(struct sockaddr_in));
#else
write(stop_pipe->fds[1], "\x00", 1);
#endif
@@ -105,12 +119,17 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_select_single(ChiakiStopPipe *sto
#else
fd_set rfds;
FD_ZERO(&rfds);
- FD_SET(stop_pipe->fds[0], &rfds);
+#if defined(__SWITCH__)
+ // push udp local socket as fd
+ int stop_fd = stop_pipe->fd;
+#else
+ int stop_fd = stop_pipe->fds[0];
+#endif
+ FD_SET(stop_fd, &rfds);
+ int nfds = stop_fd;
fd_set wfds;
FD_ZERO(&wfds);
-
- int nfds = stop_pipe->fds[0];
if(!CHIAKI_SOCKET_IS_INVALID(fd))
{
FD_SET(fd, write ? &wfds : &rfds);
@@ -128,11 +147,16 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_select_single(ChiakiStopPipe *sto
timeout = &timeout_s;
}
- int r = select(nfds, &rfds, write ? &wfds : NULL, NULL, timeout);
+ int r;
+ do
+ {
+ r = select(nfds, &rfds, write ? &wfds : NULL, NULL, timeout);
+ } while(r < 0 && errno == EINTR);
+
if(r < 0)
return CHIAKI_ERR_UNKNOWN;
- if(FD_ISSET(stop_pipe->fds[0], &rfds))
+ if(FD_ISSET(stop_fd, &rfds))
return CHIAKI_ERR_CANCELED;
if(!CHIAKI_SOCKET_IS_INVALID(fd) && FD_ISSET(fd, write ? &wfds : &rfds))
@@ -223,6 +247,12 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_reset(ChiakiStopPipe *stop_pipe)
#ifdef _WIN32
BOOL r = WSAResetEvent(stop_pipe->event);
return r ? CHIAKI_ERR_SUCCESS : CHIAKI_ERR_UNKNOWN;
+#elif defined(__SWITCH__)
+ //FIXME
+ uint8_t v;
+ int r;
+ while((r = read(stop_pipe->fd, &v, sizeof(v))) > 0);
+ return r < 0 ? CHIAKI_ERR_UNKNOWN : CHIAKI_ERR_SUCCESS;
#else
uint8_t v;
int r;
diff --git a/lib/src/streamconnection.c b/lib/src/streamconnection.c
index fde546c..7187c1b 100644
--- a/lib/src/streamconnection.c
+++ b/lib/src/streamconnection.c
@@ -1,21 +1,7 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+#include "chiaki/common.h"
#include
#include
#include
@@ -59,7 +45,11 @@ void chiaki_session_send_event(ChiakiSession *session, ChiakiEvent *event);
static void stream_connection_takion_cb(ChiakiTakionEvent *event, void *user);
static void stream_connection_takion_data(ChiakiStreamConnection *stream_connection, ChiakiTakionMessageDataType data_type, uint8_t *buf, size_t buf_size);
+static void stream_connection_takion_data_protobuf(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size);
+static void stream_connection_takion_data_rumble(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size);
+static void stream_connection_takion_data_trigger_effects(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size);
static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream_connection);
+static ChiakiErrorCode stream_connection_send_controller_connection(ChiakiStreamConnection *stream_connection);
static ChiakiErrorCode stream_connection_send_disconnect(ChiakiStreamConnection *stream_connection);
static void stream_connection_takion_data_idle(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size);
static void stream_connection_takion_data_expect_bang(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size);
@@ -68,7 +58,6 @@ static ChiakiErrorCode stream_connection_send_streaminfo_ack(ChiakiStreamConnect
static void stream_connection_takion_av(ChiakiStreamConnection *stream_connection, ChiakiTakionAVPacket *packet);
static ChiakiErrorCode stream_connection_send_heartbeat(ChiakiStreamConnection *stream_connection);
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_init(ChiakiStreamConnection *stream_connection, ChiakiSession *session)
{
stream_connection->session = session;
@@ -86,10 +75,18 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_init(ChiakiStreamConnecti
if(err != CHIAKI_ERR_SUCCESS)
goto error_state_mutex;
- err = chiaki_mutex_init(&stream_connection->feedback_sender_mutex, false);
+ err = chiaki_packet_stats_init(&stream_connection->packet_stats);
if(err != CHIAKI_ERR_SUCCESS)
goto error_state_cond;
+ stream_connection->video_receiver = NULL;
+ stream_connection->audio_receiver = NULL;
+ stream_connection->haptics_receiver = NULL;
+
+ err = chiaki_mutex_init(&stream_connection->feedback_sender_mutex, false);
+ if(err != CHIAKI_ERR_SUCCESS)
+ goto error_packet_stats;
+
stream_connection->state = STATE_IDLE;
stream_connection->state_finished = false;
stream_connection->state_failed = false;
@@ -99,6 +96,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_init(ChiakiStreamConnecti
return CHIAKI_ERR_SUCCESS;
+error_packet_stats:
+ chiaki_packet_stats_fini(&stream_connection->packet_stats);
error_state_cond:
chiaki_cond_fini(&stream_connection->state_cond);
error_state_mutex:
@@ -116,13 +115,14 @@ CHIAKI_EXPORT void chiaki_stream_connection_fini(ChiakiStreamConnection *stream_
free(stream_connection->ecdh_secret);
+ chiaki_packet_stats_fini(&stream_connection->packet_stats);
+
chiaki_mutex_fini(&stream_connection->feedback_sender_mutex);
chiaki_cond_fini(&stream_connection->state_cond);
chiaki_mutex_fini(&stream_connection->state_mutex);
}
-
static bool state_finished_cond_check(void *user)
{
ChiakiStreamConnection *stream_connection = user;
@@ -146,7 +146,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio
takion_info.ip_dontfrag = false;
takion_info.enable_crypt = true;
- takion_info.protocol_version = 9;
+ takion_info.enable_dualsense = session->connect_info.enable_dualsense;
+ takion_info.protocol_version = chiaki_target_is_ps5(session->target) ? 12 : 9;
takion_info.cb = stream_connection_takion_cb;
takion_info.cb_user = stream_connection;
@@ -160,6 +161,29 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio
goto quit_label; \
} } while(0)
+ stream_connection->audio_receiver = chiaki_audio_receiver_new(session, &stream_connection->packet_stats);
+ if(!stream_connection->audio_receiver)
+ {
+ CHIAKI_LOGE(session->log, "StreamConnection failed to initialize Audio Receiver");
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ stream_connection->haptics_receiver = chiaki_audio_receiver_new(session, NULL);
+ if(!stream_connection->haptics_receiver)
+ {
+ CHIAKI_LOGE(session->log, "StreamConnection failed to initialize Haptics Receiver");
+ err = CHIAKI_ERR_UNKNOWN;
+ goto err_audio_receiver;
+ }
+
+ stream_connection->video_receiver = chiaki_video_receiver_new(session, &stream_connection->packet_stats);
+ if(!stream_connection->video_receiver)
+ {
+ CHIAKI_LOGE(session->log, "StreamConnection failed to initialize Video Receiver");
+ err = CHIAKI_ERR_UNKNOWN;
+ goto err_haptics_receiver;
+ }
+
stream_connection->state = STATE_TAKION_CONNECT;
stream_connection->state_finished = false;
stream_connection->state_failed = false;
@@ -169,7 +193,15 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio
{
CHIAKI_LOGE(session->log, "StreamConnection connect failed");
chiaki_mutex_unlock(&stream_connection->state_mutex);
- return err;
+ goto err_video_receiver;
+ }
+
+ ChiakiCongestionControl congestion_control;
+ err = chiaki_congestion_control_start(&congestion_control, &stream_connection->takion, &stream_connection->packet_stats);
+ if(err != CHIAKI_ERR_SUCCESS)
+ {
+ CHIAKI_LOGE(session->log, "StreamConnection failed to start Congestion Control");
+ goto close_takion;
}
err = chiaki_cond_timedwait_pred(&stream_connection->state_cond, &stream_connection->state_mutex, EXPECT_TIMEOUT_MS, state_finished_cond_check, stream_connection);
@@ -178,7 +210,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stream_connection_run(ChiakiStreamConnectio
if(err != CHIAKI_ERR_SUCCESS)
{
CHIAKI_LOGE(session->log, "StreamConnection Takion connect failed");
- goto close_takion;
+ goto err_congestion_control;
}
CHIAKI_LOGI(session->log, "StreamConnection sending big");
@@ -288,12 +320,27 @@ disconnect:
err = CHIAKI_ERR_DISCONNECTED;
}
+err_congestion_control:
+ chiaki_congestion_control_stop(&congestion_control);
+
close_takion:
chiaki_mutex_unlock(&stream_connection->state_mutex);
chiaki_takion_close(&stream_connection->takion);
CHIAKI_LOGI(session->log, "StreamConnection closed takion");
+err_video_receiver:
+ chiaki_video_receiver_free(stream_connection->video_receiver);
+ stream_connection->video_receiver = NULL;
+
+err_haptics_receiver:
+ chiaki_audio_receiver_free(stream_connection->haptics_receiver);
+ stream_connection->haptics_receiver = NULL;
+
+err_audio_receiver:
+ chiaki_audio_receiver_free(stream_connection->audio_receiver);
+ stream_connection->audio_receiver = NULL;
+
return err;
}
@@ -337,9 +384,24 @@ static void stream_connection_takion_cb(ChiakiTakionEvent *event, void *user)
static void stream_connection_takion_data(ChiakiStreamConnection *stream_connection, ChiakiTakionMessageDataType data_type, uint8_t *buf, size_t buf_size)
{
- if(data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF)
- return;
+ switch(data_type)
+ {
+ case CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF:
+ stream_connection_takion_data_protobuf(stream_connection, buf, buf_size);
+ break;
+ case CHIAKI_TAKION_MESSAGE_DATA_TYPE_RUMBLE:
+ stream_connection_takion_data_rumble(stream_connection, buf, buf_size);
+ break;
+ case CHIAKI_TAKION_MESSAGE_DATA_TYPE_TRIGGER_EFFECTS:
+ stream_connection_takion_data_trigger_effects(stream_connection, buf, buf_size);
+ break;
+ default:
+ break;
+ }
+}
+static void stream_connection_takion_data_protobuf(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size)
+{
chiaki_mutex_lock(&stream_connection->state_mutex);
switch(stream_connection->state)
{
@@ -356,6 +418,40 @@ static void stream_connection_takion_data(ChiakiStreamConnection *stream_connect
chiaki_mutex_unlock(&stream_connection->state_mutex);
}
+static void stream_connection_takion_data_rumble(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size)
+{
+ if(buf_size < 3)
+ {
+ CHIAKI_LOGE(stream_connection->log, "StreamConnection got rumble packet with size %#llx < 3",
+ (unsigned long long)buf_size);
+ return;
+ }
+ ChiakiEvent event = { 0 };
+ event.type = CHIAKI_EVENT_RUMBLE;
+ event.rumble.unknown = buf[0];
+ event.rumble.left = buf[1];
+ event.rumble.right = buf[2];
+ chiaki_session_send_event(stream_connection->session, &event);
+}
+
+
+static void stream_connection_takion_data_trigger_effects(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size)
+{
+ if(buf_size < 25)
+ {
+ CHIAKI_LOGE(stream_connection->log, "StreamConnection got trigger effects packet with size %#llx < 25",
+ (unsigned long long)buf_size);
+ return;
+ }
+ ChiakiEvent event = { 0 };
+ event.type = CHIAKI_EVENT_TRIGGER_EFFECTS;
+ event.trigger_effects.type_left = buf[1];
+ event.trigger_effects.type_right = buf[2];
+ memcpy(&event.trigger_effects.left, buf + 5, 10);
+ memcpy(&event.trigger_effects.right, buf + 15, 10);
+ chiaki_session_send_event(stream_connection->session, &event);
+}
+
static void stream_connection_takion_data_handle_disconnect(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size)
{
tkproto_TakionMessage msg;
@@ -401,7 +497,7 @@ static void stream_connection_takion_data_idle(ChiakiStreamConnection *stream_co
return;
}
- CHIAKI_LOGV(stream_connection->log, "StreamConnection received data");
+ CHIAKI_LOGV(stream_connection->log, "StreamConnection received data with msg.type == %d", msg.type);
chiaki_log_hexdump(stream_connection->log, CHIAKI_LOG_VERBOSE, buf, buf_size);
if(msg.type == tkproto_TakionMessage_PayloadType_DISCONNECT)
@@ -463,7 +559,8 @@ static void stream_connection_takion_data_expect_bang(ChiakiStreamConnection *st
return;
}
- CHIAKI_LOGE(stream_connection->log, "StreamConnection expected bang payload but received something else");
+ CHIAKI_LOGE(stream_connection->log, "StreamConnection expected bang payload but received something else: %d", msg.type);
+ chiaki_log_hexdump(stream_connection->log, CHIAKI_LOG_VERBOSE, buf, buf_size);
return;
}
@@ -525,6 +622,12 @@ static void stream_connection_takion_data_expect_bang(ChiakiStreamConnection *st
// stream_connection->state_mutex is expected to be locked by the caller of this function
stream_connection->state_finished = true;
chiaki_cond_signal(&stream_connection->state_cond);
+ err = stream_connection_send_controller_connection(stream_connection);
+ if(err != CHIAKI_ERR_SUCCESS)
+ {
+ CHIAKI_LOGE(stream_connection->log, "StreamConnection failed to send controller connection");
+ goto error;
+ }
return;
error:
stream_connection->state_failed = true;
@@ -578,7 +681,6 @@ static bool pb_decode_resolution(pb_istream_t *stream, const pb_field_t *field,
return true;
}
-
static void stream_connection_takion_data_expect_streaminfo(ChiakiStreamConnection *stream_connection, uint8_t *buf, size_t buf_size)
{
tkproto_TakionMessage msg;
@@ -628,9 +730,9 @@ static void stream_connection_takion_data_expect_streaminfo(ChiakiStreamConnecti
ChiakiAudioHeader audio_header_s;
chiaki_audio_header_load(&audio_header_s, audio_header);
- chiaki_audio_receiver_stream_info(stream_connection->session->audio_receiver, &audio_header_s);
+ chiaki_audio_receiver_stream_info(stream_connection->audio_receiver, &audio_header_s);
- chiaki_video_receiver_stream_info(stream_connection->session->video_receiver,
+ chiaki_video_receiver_stream_info(stream_connection->video_receiver,
decode_resolutions_context.video_profiles,
decode_resolutions_context.video_profiles_count);
@@ -647,11 +749,9 @@ error:
chiaki_cond_signal(&stream_connection->state_cond);
}
-
-
static bool chiaki_pb_encode_zero_encrypted_key(pb_ostream_t *stream, const pb_field_t *field, void *const *arg)
{
- if (!pb_encode_tag_for_field(stream, field))
+ if(!pb_encode_tag_for_field(stream, field))
return false;
uint8_t data[] = { 0, 0, 0, 0 };
return pb_encode_string(stream, data, sizeof(data));
@@ -664,6 +764,7 @@ static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream
ChiakiSession *session = stream_connection->session;
ChiakiLaunchSpec launch_spec;
+ launch_spec.target = session->target;
launch_spec.mtu = session->mtu_in;
launch_spec.rtt = session->rtt_us / 1000;
launch_spec.handshake_key = session->handshake_key;
@@ -671,6 +772,7 @@ static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream
launch_spec.width = session->connect_info.video_profile.width;
launch_spec.height = session->connect_info.video_profile.height;
launch_spec.max_fps = session->connect_info.video_profile.max_fps;
+ launch_spec.codec = session->connect_info.video_profile.codec;
launch_spec.bw_kbps_sent = session->connect_info.video_profile.bitrate;
union
@@ -725,7 +827,7 @@ static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream
msg.type = tkproto_TakionMessage_PayloadType_BIG;
msg.has_big_payload = true;
- msg.big_payload.client_version = 9;
+ msg.big_payload.client_version = stream_connection->takion.version;
msg.big_payload.session_key.arg = session->session_id;
msg.big_payload.session_key.funcs.encode = chiaki_pb_encode_string;
msg.big_payload.launch_spec.arg = launch_spec_buf.b64;
@@ -753,6 +855,37 @@ static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream
return err;
}
+static ChiakiErrorCode stream_connection_send_controller_connection(ChiakiStreamConnection *stream_connection)
+{
+ ChiakiSession *session = stream_connection->session;
+ tkproto_TakionMessage msg;
+ memset(&msg, 0, sizeof(msg));
+
+ msg.type = tkproto_TakionMessage_PayloadType_CONTROLLERCONNECTION;
+ msg.has_controller_connection_payload = true;
+ msg.controller_connection_payload.has_connected = true;
+ msg.controller_connection_payload.connected = true;
+ msg.controller_connection_payload.has_controller_id = false;
+ msg.controller_connection_payload.has_controller_type = true;
+ msg.controller_connection_payload.controller_type = session->connect_info.enable_dualsense
+ ? tkproto_ControllerConnectionPayload_ControllerType_DUALSENSE
+ : tkproto_ControllerConnectionPayload_ControllerType_DUALSHOCK4;
+
+ uint8_t buf[2048];
+ size_t buf_size;
+
+ pb_ostream_t stream = pb_ostream_from_buffer(buf, sizeof(buf));
+ bool pbr = pb_encode(&stream, tkproto_TakionMessage_fields, &msg);
+ if(!pbr)
+ {
+ CHIAKI_LOGE(stream_connection->log, "StreamConnection controller connection protobuf encoding failed");
+ return CHIAKI_ERR_UNKNOWN;
+ }
+
+ buf_size = stream.bytes_written;
+ return chiaki_takion_send_message_data(&stream_connection->takion, 1, 1, buf, buf_size, NULL);
+}
+
static ChiakiErrorCode stream_connection_send_streaminfo_ack(ChiakiStreamConnection *stream_connection)
{
tkproto_TakionMessage msg;
@@ -803,18 +936,18 @@ static ChiakiErrorCode stream_connection_send_disconnect(ChiakiStreamConnection
return err;
}
-
static void stream_connection_takion_av(ChiakiStreamConnection *stream_connection, ChiakiTakionAVPacket *packet)
{
chiaki_gkcrypt_decrypt(stream_connection->gkcrypt_remote, packet->key_pos + CHIAKI_GKCRYPT_BLOCK_SIZE, packet->data, packet->data_size);
if(packet->is_video)
- chiaki_video_receiver_av_packet(stream_connection->session->video_receiver, packet);
+ chiaki_video_receiver_av_packet(stream_connection->video_receiver, packet);
+ else if(packet->is_haptics)
+ chiaki_audio_receiver_av_packet(stream_connection->haptics_receiver, packet);
else
- chiaki_audio_receiver_av_packet(stream_connection->session->audio_receiver, packet);
+ chiaki_audio_receiver_av_packet(stream_connection->audio_receiver, packet);
}
-
static ChiakiErrorCode stream_connection_send_heartbeat(ChiakiStreamConnection *stream_connection)
{
tkproto_TakionMessage msg = { 0 };
diff --git a/lib/src/takion.c b/lib/src/takion.c
index 64e4c69..baae28c 100644
--- a/lib/src/takion.c
+++ b/lib/src/takion.c
@@ -1,23 +1,10 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+#include "chiaki/feedback.h"
#include
#include
#include
+#include
#include
#include
@@ -28,6 +15,11 @@
#ifdef _WIN32
#include
+#elif defined(__SWITCH__)
+#include
+#include
+#include
+#include
#else
#include
#include
@@ -64,9 +56,9 @@ typedef enum takion_packet_type_t {
TAKION_PACKET_TYPE_HANDSHAKE = 4,
TAKION_PACKET_TYPE_CONGESTION = 5,
TAKION_PACKET_TYPE_FEEDBACK_STATE = 6,
- TAKION_PACKET_TYPE_RUMBLE_EVENT = 7,
TAKION_PACKET_TYPE_CLIENT_INFO = 8,
- TAKION_PACKET_TYPE_PAD_INFO_EVENT = 9
+ TAKION_PACKET_TYPE_PAD_INFO_EVENT = 9,
+ TAKION_PACKET_TYPE_PAD_ADAPTIVE_TRIGGERS = 11,
} TakionPacketType;
/**
@@ -81,6 +73,8 @@ int takion_packet_type_mac_offset(TakionPacketType type)
case TAKION_PACKET_TYPE_VIDEO:
case TAKION_PACKET_TYPE_AUDIO:
return 0xa;
+ case TAKION_PACKET_TYPE_CONGESTION:
+ return 7;
default:
return -1;
}
@@ -98,6 +92,8 @@ int takion_packet_type_key_pos_offset(TakionPacketType type)
case TAKION_PACKET_TYPE_VIDEO:
case TAKION_PACKET_TYPE_AUDIO:
return 0xe;
+ case TAKION_PACKET_TYPE_CONGESTION:
+ return 0xb;
default:
return -1;
}
@@ -112,12 +108,11 @@ typedef enum takion_chunk_type_t {
TAKION_CHUNK_TYPE_COOKIE_ACK = 0xb,
} TakionChunkType;
-
typedef struct takion_message_t
{
uint32_t tag;
//uint8_t zero[4];
- uint32_t key_pos;
+ uint64_t key_pos;
uint8_t chunk_type;
uint8_t chunk_flags;
@@ -125,7 +120,6 @@ typedef struct takion_message_t
uint8_t *payload;
} TakionMessage;
-
typedef struct takion_message_payload_init_t
{
uint32_t tag;
@@ -157,14 +151,12 @@ typedef struct
uint16_t channel;
} TakionDataPacketEntry;
-
typedef struct chiaki_takion_postponed_packet_t
{
uint8_t *buf;
size_t buf_size;
} ChiakiTakionPostponedPacket;
-
static void *takion_thread_func(void *user);
static void takion_handle_packet(ChiakiTakion *takion, uint8_t *buf, size_t buf_size);
static ChiakiErrorCode takion_handle_packet_mac(ChiakiTakion *takion, uint8_t base_type, uint8_t *buf, size_t buf_size);
@@ -172,7 +164,7 @@ static void takion_handle_packet_message(ChiakiTakion *takion, uint8_t *buf, siz
static void takion_handle_packet_message_data(ChiakiTakion *takion, uint8_t *packet_buf, size_t packet_buf_size, uint8_t type_b, uint8_t *payload, size_t payload_size);
static void takion_handle_packet_message_data_ack(ChiakiTakion *takion, uint8_t flags, uint8_t *buf, size_t buf_size);
static ChiakiErrorCode takion_parse_message(ChiakiTakion *takion, uint8_t *buf, size_t buf_size, TakionMessage *msg);
-static void takion_write_message_header(uint8_t *buf, uint32_t tag, uint32_t key_pos, uint8_t chunk_type, uint8_t chunk_flags, size_t payload_data_size);
+static void takion_write_message_header(uint8_t *buf, uint32_t tag, uint64_t key_pos, uint8_t chunk_type, uint8_t chunk_flags, size_t payload_data_size);
static ChiakiErrorCode takion_send_message_init(ChiakiTakion *takion, TakionMessagePayloadInit *payload);
static ChiakiErrorCode takion_send_message_cookie(ChiakiTakion *takion, uint8_t *cookie);
static ChiakiErrorCode takion_recv(ChiakiTakion *takion, uint8_t *buf, size_t *buf_size, uint64_t timeout_ms);
@@ -185,8 +177,9 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_connect(ChiakiTakion *takion, Chiaki
ChiakiErrorCode ret = CHIAKI_ERR_SUCCESS;
takion->log = info->log;
+ takion->version = info->protocol_version;
- switch(info->protocol_version)
+ switch(takion->version)
{
case 7:
takion->av_packet_parse = chiaki_takion_v7_av_packet_parse;
@@ -194,8 +187,11 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_connect(ChiakiTakion *takion, Chiaki
case 9:
takion->av_packet_parse = chiaki_takion_v9_av_packet_parse;
break;
+ case 12:
+ takion->av_packet_parse = chiaki_takion_v12_av_packet_parse;
+ break;
default:
- CHIAKI_LOGE(takion->log, "Unknown Takion Protocol Version %u", (unsigned int)info->protocol_version);
+ CHIAKI_LOGE(takion->log, "Unknown Takion Protocol Version %u", (unsigned int)takion->version);
return CHIAKI_ERR_INVALID_DATA;
}
@@ -220,6 +216,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_connect(ChiakiTakion *takion, Chiaki
takion->postponed_packets = NULL;
takion->postponed_packets_size = 0;
takion->postponed_packets_count = 0;
+ takion->enable_dualsense = info->enable_dualsense;
CHIAKI_LOGI(takion->log, "Takion connecting (version %u)", (unsigned int)info->protocol_version);
@@ -249,19 +246,22 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_connect(ChiakiTakion *takion, Chiaki
if(info->ip_dontfrag)
{
-#if __APPLE__
- CHIAKI_LOGW(takion->log, "Don't fragment is not supported on macOS, MTU values may be incorrect.");
-#else
#if defined(_WIN32)
const DWORD dontfragment_val = 1;
r = setsockopt(takion->sock, IPPROTO_IP, IP_DONTFRAGMENT, (const void *)&dontfragment_val, sizeof(dontfragment_val));
-#elif defined(__FreeBSD__)
+#elif defined(__FreeBSD__) || defined(__SWITCH__)
const int dontfrag_val = 1;
r = setsockopt(takion->sock, IPPROTO_IP, IP_DONTFRAG, (const void *)&dontfrag_val, sizeof(dontfrag_val));
-#else
+#elif defined(IP_PMTUDISC_DO)
const int mtu_discover_val = IP_PMTUDISC_DO;
r = setsockopt(takion->sock, IPPROTO_IP, IP_MTU_DISCOVER, (const void *)&mtu_discover_val, sizeof(mtu_discover_val));
+#else
+ // macOS and OpenBSD
+ CHIAKI_LOGW(takion->log, "Don't fragment is not supported on this platform, MTU values may be incorrect.");
+#define NO_DONTFRAG
#endif
+
+#ifndef NO_DONTFRAG
if(r < 0)
{
CHIAKI_LOGE(takion->log, "Takion failed to setsockopt IP_MTU_DISCOVER: %s", strerror(errno));
@@ -311,7 +311,7 @@ CHIAKI_EXPORT void chiaki_takion_close(ChiakiTakion *takion)
chiaki_mutex_fini(&takion->gkcrypt_local_mutex);
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_crypt_advance_key_pos(ChiakiTakion *takion, size_t data_size, size_t *key_pos)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_crypt_advance_key_pos(ChiakiTakion *takion, size_t data_size, uint64_t *key_pos)
{
ChiakiErrorCode err = chiaki_mutex_lock(&takion->gkcrypt_local_mutex);
if(err != CHIAKI_ERR_SUCCESS)
@@ -319,7 +319,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_crypt_advance_key_pos(ChiakiTakion *
if(takion->gkcrypt_local)
{
- size_t cur = takion->key_pos_local;
+ uint64_t cur = takion->key_pos_local;
if(SIZE_MAX - cur < data_size)
{
chiaki_mutex_unlock(&takion->gkcrypt_local_mutex);
@@ -344,8 +344,26 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_raw(ChiakiTakion *takion, const
return CHIAKI_ERR_SUCCESS;
}
+static ChiakiErrorCode chiaki_takion_packet_read_key_pos(ChiakiTakion *takion, uint8_t *buf, size_t buf_size, uint64_t *key_pos_out)
+{
+ if(buf_size < 1)
+ return CHIAKI_ERR_BUF_TOO_SMALL;
-static ChiakiErrorCode chiaki_takion_packet_mac(ChiakiGKCrypt *crypt, uint8_t *buf, size_t buf_size, uint8_t *mac_out, uint8_t *mac_old_out, ChiakiTakionPacketKeyPos *key_pos_out)
+ TakionPacketType base_type = buf[0] & TAKION_PACKET_BASE_TYPE_MASK;
+ int key_pos_offset = takion_packet_type_key_pos_offset(base_type);
+ if(key_pos_offset < 0)
+ return CHIAKI_ERR_INVALID_DATA;
+
+ if(buf_size < key_pos_offset + sizeof(uint32_t))
+ return CHIAKI_ERR_BUF_TOO_SMALL;
+
+ uint32_t key_pos_low = ntohl(*((chiaki_unaligned_uint32_t *)(buf + key_pos_offset)));
+ *key_pos_out = chiaki_key_state_request_pos(&takion->key_state, key_pos_low, false);
+
+ return CHIAKI_ERR_SUCCESS;
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_packet_mac(ChiakiGKCrypt *crypt, uint8_t *buf, size_t buf_size, uint64_t key_pos, uint8_t *mac_out, uint8_t *mac_old_out)
{
if(buf_size < 1)
return CHIAKI_ERR_BUF_TOO_SMALL;
@@ -356,7 +374,7 @@ static ChiakiErrorCode chiaki_takion_packet_mac(ChiakiGKCrypt *crypt, uint8_t *b
if(mac_offset < 0 || key_pos_offset < 0)
return CHIAKI_ERR_INVALID_DATA;
- if(buf_size < mac_offset + CHIAKI_GKCRYPT_GMAC_SIZE || buf_size < key_pos_offset + sizeof(ChiakiTakionPacketKeyPos))
+ if(buf_size < mac_offset + CHIAKI_GKCRYPT_GMAC_SIZE || buf_size < key_pos_offset + sizeof(uint32_t))
return CHIAKI_ERR_BUF_TOO_SMALL;
if(mac_old_out)
@@ -364,31 +382,32 @@ static ChiakiErrorCode chiaki_takion_packet_mac(ChiakiGKCrypt *crypt, uint8_t *b
memset(buf + mac_offset, 0, CHIAKI_GKCRYPT_GMAC_SIZE);
- ChiakiTakionPacketKeyPos key_pos = ntohl(*((ChiakiTakionPacketKeyPos *)(buf + key_pos_offset)));
-
if(crypt)
{
- if(base_type == TAKION_PACKET_TYPE_CONTROL)
- memset(buf + key_pos_offset, 0, sizeof(ChiakiTakionPacketKeyPos));
+ uint8_t key_pos_tmp[sizeof(uint32_t)];
+ if(base_type == TAKION_PACKET_TYPE_CONTROL || base_type == TAKION_PACKET_TYPE_CONGESTION)
+ {
+ memcpy(key_pos_tmp, buf + key_pos_offset, sizeof(uint32_t));
+ memset(buf + key_pos_offset, 0, sizeof(uint32_t));
+ }
chiaki_gkcrypt_gmac(crypt, key_pos, buf, buf_size, buf + mac_offset);
- *((ChiakiTakionPacketKeyPos *)(buf + key_pos_offset)) = htonl(key_pos);
+ if(base_type == TAKION_PACKET_TYPE_CONTROL || base_type == TAKION_PACKET_TYPE_CONGESTION)
+ memcpy(buf + key_pos_offset, key_pos_tmp, sizeof(uint32_t));
}
- if(key_pos_out)
- *key_pos_out = key_pos;
-
- memcpy(mac_out, buf + mac_offset, CHIAKI_GKCRYPT_GMAC_SIZE);
+ if(mac_out)
+ memcpy(mac_out, buf + mac_offset, CHIAKI_GKCRYPT_GMAC_SIZE);
return CHIAKI_ERR_SUCCESS;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send(ChiakiTakion *takion, uint8_t *buf, size_t buf_size)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send(ChiakiTakion *takion, uint8_t *buf, size_t buf_size, uint64_t key_pos)
{
ChiakiErrorCode err = chiaki_mutex_lock(&takion->gkcrypt_local_mutex);
if(err != CHIAKI_ERR_SUCCESS)
return err;
uint8_t mac[CHIAKI_GKCRYPT_GMAC_SIZE];
- err = chiaki_takion_packet_mac(takion->gkcrypt_local, buf, buf_size, mac, NULL, NULL);
+ err = chiaki_takion_packet_mac(takion->gkcrypt_local, buf, buf_size, key_pos, mac, NULL);
chiaki_mutex_unlock(&takion->gkcrypt_local_mutex);
if(err != CHIAKI_ERR_SUCCESS)
return err;
@@ -404,7 +423,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_message_data(ChiakiTakion *taki
// TODO: can we make this more memory-efficient?
// TODO: split packet if necessary?
- size_t key_pos;
+ uint64_t key_pos;
ChiakiErrorCode err = chiaki_takion_crypt_advance_key_pos(takion, buf_size, &key_pos);
if(err != CHIAKI_ERR_SUCCESS)
return err;
@@ -431,7 +450,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_message_data(ChiakiTakion *taki
*(msg_payload + 8) = 0;
memcpy(msg_payload + 9, buf, buf_size);
- err = chiaki_takion_send(takion, packet_buf, packet_size); // will alter packet_buf with gmac
+ err = chiaki_takion_send(takion, packet_buf, packet_size, key_pos); // will alter packet_buf with gmac
if(err != CHIAKI_ERR_SUCCESS)
{
CHIAKI_LOGE(takion->log, "Takion failed to send data packet: %s", chiaki_error_string(err));
@@ -452,7 +471,7 @@ static ChiakiErrorCode chiaki_takion_send_message_data_ack(ChiakiTakion *takion,
uint8_t buf[1 + TAKION_MESSAGE_HEADER_SIZE + 0xc];
buf[0] = TAKION_PACKET_TYPE_CONTROL;
- size_t key_pos;
+ uint64_t key_pos;
ChiakiErrorCode err = chiaki_takion_crypt_advance_key_pos(takion, sizeof(buf), &key_pos);
if(err != CHIAKI_ERR_SUCCESS)
return err;
@@ -465,31 +484,29 @@ static ChiakiErrorCode chiaki_takion_send_message_data_ack(ChiakiTakion *takion,
*((chiaki_unaligned_uint16_t *)(data_ack + 8)) = 0;
*((chiaki_unaligned_uint16_t *)(data_ack + 0xa)) = 0;
- return chiaki_takion_send(takion, buf, sizeof(buf));
+ return chiaki_takion_send(takion, buf, sizeof(buf), key_pos);
+}
+
+CHIAKI_EXPORT void chiaki_takion_format_congestion(uint8_t *buf, ChiakiTakionCongestionPacket *packet, uint64_t key_pos)
+{
+ buf[0] = TAKION_PACKET_TYPE_CONGESTION;
+ *((chiaki_unaligned_uint16_t *)(buf + 1)) = htons(packet->word_0);
+ *((chiaki_unaligned_uint16_t *)(buf + 3)) = htons(packet->received);
+ *((chiaki_unaligned_uint16_t *)(buf + 5)) = htons(packet->lost);
+ *((chiaki_unaligned_uint32_t *)(buf + 7)) = 0;
+ *((chiaki_unaligned_uint32_t *)(buf + 0xb)) = htonl((uint32_t)key_pos);
}
CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_congestion(ChiakiTakion *takion, ChiakiTakionCongestionPacket *packet)
{
- uint8_t buf[0xf];
- memset(buf, 0, sizeof(buf));
- buf[0] = TAKION_PACKET_TYPE_CONGESTION;
- *((chiaki_unaligned_uint16_t *)(buf + 1)) = htons(packet->word_0);
- *((chiaki_unaligned_uint16_t *)(buf + 3)) = htons(packet->word_1);
- *((chiaki_unaligned_uint16_t *)(buf + 5)) = htons(packet->word_2);
-
- ChiakiErrorCode err = chiaki_mutex_lock(&takion->gkcrypt_local_mutex);
- if(err != CHIAKI_ERR_SUCCESS)
- return err;
- *((chiaki_unaligned_uint32_t *)(buf + 0xb)) = htonl((uint32_t)takion->key_pos_local); // TODO: is this correct? shouldn't key_pos be 0 for mac calculation?
- err = chiaki_gkcrypt_gmac(takion->gkcrypt_local, takion->key_pos_local, buf, sizeof(buf), buf + 7);
- takion->key_pos_local += sizeof(buf);
- chiaki_mutex_unlock(&takion->gkcrypt_local_mutex);
+ uint64_t key_pos;
+ ChiakiErrorCode err = chiaki_takion_crypt_advance_key_pos(takion, CHIAKI_TAKION_CONGESTION_PACKET_SIZE, &key_pos);
if(err != CHIAKI_ERR_SUCCESS)
return err;
- //chiaki_log_hexdump(takion->log, CHIAKI_LOG_DEBUG, buf, sizeof(buf));
-
- return chiaki_takion_send_raw(takion, buf, sizeof(buf));
+ uint8_t buf[CHIAKI_TAKION_CONGESTION_PACKET_SIZE];
+ chiaki_takion_format_congestion(buf, packet, key_pos);
+ return chiaki_takion_send(takion, buf, sizeof(buf), key_pos);
}
static ChiakiErrorCode takion_send_feedback_packet(ChiakiTakion *takion, uint8_t *buf, size_t buf_size)
@@ -502,7 +519,7 @@ static ChiakiErrorCode takion_send_feedback_packet(ChiakiTakion *takion, uint8_t
if(err != CHIAKI_ERR_SUCCESS)
return err;
- size_t key_pos;
+ uint64_t key_pos;
err = chiaki_takion_crypt_advance_key_pos(takion, payload_size + CHIAKI_GKCRYPT_BLOCK_SIZE, &key_pos);
if(err != CHIAKI_ERR_SUCCESS)
goto beach;
@@ -526,14 +543,24 @@ beach:
CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_feedback_state(ChiakiTakion *takion, ChiakiSeqNum16 seq_num, ChiakiFeedbackState *feedback_state)
{
- uint8_t buf[0xc + CHIAKI_FEEDBACK_STATE_BUF_SIZE];
+ uint8_t buf[0xc + CHIAKI_FEEDBACK_STATE_BUF_SIZE_MAX];
buf[0] = TAKION_PACKET_TYPE_FEEDBACK_STATE;
*((chiaki_unaligned_uint16_t *)(buf + 1)) = htons(seq_num);
buf[3] = 0; // TODO
*((chiaki_unaligned_uint32_t *)(buf + 4)) = 0; // key pos
*((chiaki_unaligned_uint32_t *)(buf + 8)) = 0; // gmac
- chiaki_feedback_state_format(buf + 0xc, feedback_state);
- return takion_send_feedback_packet(takion, buf, sizeof(buf));
+ size_t buf_sz;
+ if(takion->version <= 9)
+ {
+ buf_sz = 0xc + CHIAKI_FEEDBACK_STATE_BUF_SIZE_V9;
+ chiaki_feedback_state_format_v9(buf + 0xc, feedback_state);
+ }
+ else
+ {
+ buf_sz = 0xc + CHIAKI_FEEDBACK_STATE_BUF_SIZE_V12;
+ chiaki_feedback_state_format_v12(buf + 0xc, feedback_state);
+ }
+ return takion_send_feedback_packet(takion, buf, buf_sz);
}
CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_send_feedback_history(ChiakiTakion *takion, ChiakiSeqNum16 seq_num, uint8_t *payload, size_t payload_size)
@@ -574,7 +601,6 @@ static ChiakiErrorCode takion_handshake(ChiakiTakion *takion, uint32_t *seq_num_
CHIAKI_LOGI(takion->log, "Takion sent init");
-
// INIT_ACK <-
TakionMessagePayloadInitAck init_ack_payload;
@@ -592,20 +618,17 @@ static ChiakiErrorCode takion_handshake(ChiakiTakion *takion, uint32_t *seq_num_
}
CHIAKI_LOGI(takion->log, "Takion received init ack with remote tag %#x, outbound streams: %#x, inbound streams: %#x",
- init_ack_payload.tag, init_ack_payload.outbound_streams, init_ack_payload.inbound_streams);
+ init_ack_payload.tag, init_ack_payload.outbound_streams, init_ack_payload.inbound_streams);
takion->tag_remote = init_ack_payload.tag;
*seq_num_remote_initial = takion->tag_remote; //init_ack_payload.initial_seq_num;
- if(init_ack_payload.outbound_streams == 0 || init_ack_payload.inbound_streams == 0
- || init_ack_payload.outbound_streams > TAKION_INBOUND_STREAMS
- || init_ack_payload.inbound_streams < TAKION_OUTBOUND_STREAMS)
+ if(init_ack_payload.outbound_streams == 0 || init_ack_payload.inbound_streams == 0 || init_ack_payload.outbound_streams > TAKION_INBOUND_STREAMS || init_ack_payload.inbound_streams < TAKION_OUTBOUND_STREAMS)
{
CHIAKI_LOGE(takion->log, "Takion min/max check failed");
return CHIAKI_ERR_INVALID_RESPONSE;
}
-
// COOKIE ->
err = takion_send_message_cookie(takion, init_ack_payload.cookie);
@@ -663,9 +686,6 @@ static void *takion_thread_func(void *user)
if(chiaki_takion_send_buffer_init(&takion->send_buffer, takion, TAKION_SEND_BUFFER_SIZE) != CHIAKI_ERR_SUCCESS)
goto error_reoder_queue;
- // TODO ChiakiCongestionControl congestion_control;
- // if(chiaki_congestion_control_start(&congestion_control, takion) != CHIAKI_ERR_SUCCESS)
- // goto beach;
if(takion->cb)
{
@@ -754,7 +774,6 @@ beach:
return NULL;
}
-
static ChiakiErrorCode takion_recv(ChiakiTakion *takion, uint8_t *buf, size_t *buf_size, uint64_t timeout_ms)
{
ChiakiErrorCode err = chiaki_stop_pipe_select_single(&takion->stop_pipe, takion->sock, false, timeout_ms);
@@ -779,7 +798,6 @@ static ChiakiErrorCode takion_recv(ChiakiTakion *takion, uint8_t *buf, size_t *b
return CHIAKI_ERR_SUCCESS;
}
-
static ChiakiErrorCode takion_handle_packet_mac(ChiakiTakion *takion, uint8_t base_type, uint8_t *buf, size_t buf_size)
{
if(!takion->gkcrypt_remote)
@@ -787,8 +805,14 @@ static ChiakiErrorCode takion_handle_packet_mac(ChiakiTakion *takion, uint8_t ba
uint8_t mac[CHIAKI_GKCRYPT_GMAC_SIZE];
uint8_t mac_expected[CHIAKI_GKCRYPT_GMAC_SIZE];
- ChiakiTakionPacketKeyPos key_pos;
- ChiakiErrorCode err = chiaki_takion_packet_mac(takion->gkcrypt_remote, buf, buf_size, mac_expected, mac, &key_pos);
+ uint64_t key_pos;
+ ChiakiErrorCode err = chiaki_takion_packet_read_key_pos(takion, buf, buf_size, &key_pos);
+ if(err != CHIAKI_ERR_SUCCESS)
+ {
+ CHIAKI_LOGE(takion->log, "Takion failed to pull key_pos out of received packet");
+ return err;
+ }
+ err = chiaki_takion_packet_mac(takion->gkcrypt_remote, buf, buf_size, key_pos, mac_expected, mac);
if(err != CHIAKI_ERR_SUCCESS)
{
CHIAKI_LOGE(takion->log, "Takion failed to calculate mac for received packet");
@@ -806,6 +830,8 @@ static ChiakiErrorCode takion_handle_packet_mac(ChiakiTakion *takion, uint8_t ba
return CHIAKI_ERR_INVALID_MAC;
}
+ chiaki_key_state_commit(&takion->key_state, key_pos);
+
return CHIAKI_ERR_SUCCESS;
}
@@ -832,7 +858,6 @@ static void takion_postpone_packet(ChiakiTakion *takion, uint8_t *buf, size_t bu
packet->buf_size = buf_size;
}
-
/**
* @param buf ownership of this buf is taken.
*/
@@ -900,7 +925,6 @@ static void takion_handle_packet_message(ChiakiTakion *takion, uint8_t *buf, siz
}
}
-
static void takion_flush_data_queue(ChiakiTakion *takion)
{
uint64_t seq_num = 0;
@@ -926,7 +950,10 @@ static void takion_flush_data_queue(ChiakiTakion *takion)
if(zero_a != 0)
CHIAKI_LOGW(takion->log, "Takion received data with unexpected nonzero %#x at buf+6", zero_a);
- if(data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_9)
+ if(data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_PROTOBUF
+ && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_RUMBLE
+ && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_TRIGGER_EFFECTS
+ && data_type != CHIAKI_TAKION_MESSAGE_DATA_TYPE_9)
{
CHIAKI_LOGW(takion->log, "Takion received data with unexpected data type %#x", data_type);
chiaki_log_hexdump(takion->log, CHIAKI_LOG_WARNING, entry->packet_buf, entry->packet_size);
@@ -1021,7 +1048,7 @@ static void takion_handle_packet_message_data_ack(ChiakiTakion *takion, uint8_t
*
* @param raw_payload_size size of the actual data of the payload excluding type_a, type_b and payload_size
*/
-static void takion_write_message_header(uint8_t *buf, uint32_t tag, uint32_t key_pos, uint8_t chunk_type, uint8_t chunk_flags, size_t payload_data_size)
+static void takion_write_message_header(uint8_t *buf, uint32_t tag, uint64_t key_pos, uint8_t chunk_type, uint8_t chunk_flags, size_t payload_data_size)
{
*((chiaki_unaligned_uint32_t *)(buf + 0)) = htonl(tag);
memset(buf + 4, 0, CHIAKI_GKCRYPT_GMAC_SIZE);
@@ -1040,7 +1067,8 @@ static ChiakiErrorCode takion_parse_message(ChiakiTakion *takion, uint8_t *buf,
}
msg->tag = ntohl(*((chiaki_unaligned_uint32_t *)buf));
- msg->key_pos = ntohl(*((chiaki_unaligned_uint32_t *)(buf + 0x8)));
+ uint32_t key_pos_low = ntohl(*((chiaki_unaligned_uint32_t *)(buf + 0x8)));
+ msg->key_pos = chiaki_key_state_request_pos(&takion->key_state, key_pos_low, true);
msg->chunk_type = buf[0xc];
msg->chunk_flags = buf[0xd];
msg->payload_size = ntohs(*((chiaki_unaligned_uint16_t *)(buf + 0xe)));
@@ -1067,7 +1095,6 @@ static ChiakiErrorCode takion_parse_message(ChiakiTakion *takion, uint8_t *buf,
return CHIAKI_ERR_SUCCESS;
}
-
static ChiakiErrorCode takion_send_message_init(ChiakiTakion *takion, TakionMessagePayloadInit *payload)
{
uint8_t message[1 + TAKION_MESSAGE_HEADER_SIZE + 0x10];
@@ -1084,8 +1111,6 @@ static ChiakiErrorCode takion_send_message_init(ChiakiTakion *takion, TakionMess
return chiaki_takion_send_raw(takion, message, sizeof(message));
}
-
-
static ChiakiErrorCode takion_send_message_cookie(ChiakiTakion *takion, uint8_t *cookie)
{
uint8_t message[1 + TAKION_MESSAGE_HEADER_SIZE + TAKION_COOKIE_SIZE];
@@ -1095,8 +1120,6 @@ static ChiakiErrorCode takion_send_message_cookie(ChiakiTakion *takion, uint8_t
return chiaki_takion_send_raw(takion, message, sizeof(message));
}
-
-
static ChiakiErrorCode takion_recv_message_init_ack(ChiakiTakion *takion, TakionMessagePayloadInitAck *payload)
{
uint8_t message[1 + TAKION_MESSAGE_HEADER_SIZE + 0x10 + TAKION_COOKIE_SIZE];
@@ -1144,7 +1167,6 @@ static ChiakiErrorCode takion_recv_message_init_ack(ChiakiTakion *takion, Takion
return CHIAKI_ERR_SUCCESS;
}
-
static ChiakiErrorCode takion_recv_message_cookie_ack(ChiakiTakion *takion)
{
uint8_t message[1 + TAKION_MESSAGE_HEADER_SIZE];
@@ -1184,7 +1206,6 @@ static ChiakiErrorCode takion_recv_message_cookie_ack(ChiakiTakion *takion)
return CHIAKI_ERR_SUCCESS;
}
-
static void takion_handle_packet_av(ChiakiTakion *takion, uint8_t base_type, uint8_t *buf, size_t buf_size)
{
// HHIxIIx
@@ -1192,7 +1213,7 @@ static void takion_handle_packet_av(ChiakiTakion *takion, uint8_t base_type, uin
assert(base_type == TAKION_PACKET_TYPE_VIDEO || base_type == TAKION_PACKET_TYPE_AUDIO);
ChiakiTakionAVPacket packet;
- ChiakiErrorCode err = takion->av_packet_parse(&packet, buf, buf_size);
+ ChiakiErrorCode err = takion->av_packet_parse(&packet, &takion->key_state, buf, buf_size);
if(err != CHIAKI_ERR_SUCCESS)
{
if(err == CHIAKI_ERR_BUF_TOO_SMALL)
@@ -1209,7 +1230,7 @@ static void takion_handle_packet_av(ChiakiTakion *takion, uint8_t base_type, uin
}
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPacket *packet, uint8_t *buf, size_t buf_size)
+static ChiakiErrorCode av_packet_parse(bool v12, ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size)
{
memset(packet, 0, sizeof(ChiakiTakionAVPacket));
@@ -1227,7 +1248,9 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPac
uint8_t *av = buf+1;
size_t av_size = buf_size-1;
- size_t av_header_size = packet->is_video ? CHIAKI_TAKION_V9_AV_HEADER_SIZE_VIDEO : CHIAKI_TAKION_V9_AV_HEADER_SIZE_AUDIO;
+ size_t av_header_size = v12
+ ? (packet->is_video ? CHIAKI_TAKION_V12_AV_HEADER_SIZE_VIDEO : CHIAKI_TAKION_V12_AV_HEADER_SIZE_AUDIO)
+ : (packet->is_video ? CHIAKI_TAKION_V9_AV_HEADER_SIZE_VIDEO : CHIAKI_TAKION_V9_AV_HEADER_SIZE_AUDIO);
if(av_size < av_header_size + 1)
return CHIAKI_ERR_BUF_TOO_SMALL;
@@ -1249,10 +1272,10 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPac
}
packet->codec = av[8];
+ uint32_t key_pos_low = ntohl(*((chiaki_unaligned_uint32_t *)(av + 0xd)));
+ packet->key_pos = chiaki_key_state_request_pos(key_state, key_pos_low, true);
- packet->key_pos = ntohl(*((chiaki_unaligned_uint32_t *)(av + 0xd)));
-
- uint8_t unknown_1 = av[0x11];
+ uint8_t unknown_1 = av[0x11]; (void)unknown_1;
av += 0x11;
av_size -= 0x11;
@@ -1286,12 +1309,29 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPac
av_size -= 3;
}
+ if(v12 && !packet->is_video)
+ {
+ packet->is_haptics = *av == 0x02;
+ av += 1;
+ av_size -= 1;
+ }
+
packet->data = av;
packet->data_size = av_size;
return CHIAKI_ERR_SUCCESS;
}
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v9_av_packet_parse(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size)
+{
+ return av_packet_parse(false, packet, key_state, buf, buf_size);
+}
+
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v12_av_packet_parse(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size)
+{
+ return av_packet_parse(true, packet, key_state, buf, buf_size);
+}
+
CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_format_header(uint8_t *buf, size_t buf_size, size_t *header_size_out, ChiakiTakionAVPacket *packet)
{
size_t header_size = CHIAKI_TAKION_V7_AV_HEADER_SIZE_BASE;
@@ -1320,7 +1360,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_format_header(uint8_t *
*(chiaki_unaligned_uint32_t *)(buf + 0xa) = 0; // unknown
- *(chiaki_unaligned_uint32_t *)(buf + 0xe) = packet->key_pos;
+ *(chiaki_unaligned_uint32_t *)(buf + 0xe) = (uint32_t)packet->key_pos;
uint8_t *cur = buf + 0x12;
if(packet->is_video)
@@ -1339,7 +1379,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_format_header(uint8_t *
return CHIAKI_ERR_SUCCESS;
}
-CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_parse(ChiakiTakionAVPacket *packet, uint8_t *buf, size_t buf_size)
+CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_parse(ChiakiTakionAVPacket *packet, ChiakiKeyState *key_state, uint8_t *buf, size_t buf_size)
{
memset(packet, 0, sizeof(ChiakiTakionAVPacket));
@@ -1397,5 +1437,4 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_v7_av_packet_parse(ChiakiTakionAVPac
packet->data_size = buf_size;
return CHIAKI_ERR_SUCCESS;
-
}
diff --git a/lib/src/takionsendbuffer.c b/lib/src/takionsendbuffer.c
index 0532c75..030476b 100644
--- a/lib/src/takionsendbuffer.c
+++ b/lib/src/takionsendbuffer.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_UNIT_TEST
diff --git a/lib/src/thread.c b/lib/src/thread.c
index 27efd2f..0539e16 100644
--- a/lib/src/thread.c
+++ b/lib/src/thread.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#define _GNU_SOURCE
@@ -24,6 +9,10 @@
#include
#include
+#ifdef __SWITCH__
+#include
+#endif
+
#if _WIN32
static DWORD WINAPI win32_thread_func(LPVOID param)
{
@@ -33,6 +22,19 @@ static DWORD WINAPI win32_thread_func(LPVOID param)
}
#endif
+#ifdef __SWITCH__
+int64_t get_thread_limit()
+{
+ uint64_t resource_limit_handle_value = INVALID_HANDLE;
+ svcGetInfo(&resource_limit_handle_value, InfoType_ResourceLimit, INVALID_HANDLE, 0);
+ int64_t thread_cur_value = 0, thread_lim_value = 0;
+ svcGetResourceLimitCurrentValue(&thread_cur_value, resource_limit_handle_value, LimitableResource_Threads);
+ svcGetResourceLimitLimitValue(&thread_lim_value, resource_limit_handle_value, LimitableResource_Threads);
+ //printf("thread_cur_value: %lu, thread_lim_value: %lu\n", thread_cur_value, thread_lim_value);
+ return thread_lim_value - thread_cur_value;
+}
+#endif
+
CHIAKI_EXPORT ChiakiErrorCode chiaki_thread_create(ChiakiThread *thread, ChiakiThreadFunc func, void *arg)
{
#if _WIN32
@@ -43,6 +45,10 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_thread_create(ChiakiThread *thread, ChiakiT
if(!thread->thread)
return CHIAKI_ERR_THREAD;
#else
+#ifdef __SWITCH__
+ if(get_thread_limit() <= 1)
+ return CHIAKI_ERR_THREAD;
+#endif
int r = pthread_create(&thread->thread, NULL, func, arg);
if(r != 0)
return CHIAKI_ERR_THREAD;
@@ -84,13 +90,13 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_thread_set_name(ChiakiThread *thread, const
if(r != 0)
return CHIAKI_ERR_THREAD;
#else
- (void)thread; (void)name;
+ (void)thread;
+ (void)name;
#endif
#endif
return CHIAKI_ERR_SUCCESS;
}
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_mutex_init(ChiakiMutex *mutex, bool rec)
{
#if _WIN32
@@ -166,9 +172,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_mutex_unlock(ChiakiMutex *mutex)
return CHIAKI_ERR_SUCCESS;
}
-
-
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_cond_init(ChiakiCond *cond)
{
#if _WIN32
@@ -208,8 +211,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_cond_fini(ChiakiCond *cond)
return CHIAKI_ERR_SUCCESS;
}
-
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_cond_wait(ChiakiCond *cond, ChiakiMutex *mutex)
{
#if _WIN32
@@ -317,7 +318,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_cond_timedwait_pred(ChiakiCond *cond, Chiak
#endif
}
return CHIAKI_ERR_SUCCESS;
-
}
CHIAKI_EXPORT ChiakiErrorCode chiaki_cond_signal(ChiakiCond *cond)
@@ -344,9 +344,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_cond_broadcast(ChiakiCond *cond)
return CHIAKI_ERR_SUCCESS;
}
-
-
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_bool_pred_cond_init(ChiakiBoolPredCond *cond)
{
cond->pred = false;
@@ -378,7 +375,6 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_bool_pred_cond_fini(ChiakiBoolPredCond *con
return CHIAKI_ERR_SUCCESS;
}
-
CHIAKI_EXPORT ChiakiErrorCode chiaki_bool_pred_cond_lock(ChiakiBoolPredCond *cond)
{
return chiaki_mutex_lock(&cond->mutex);
diff --git a/lib/src/time.c b/lib/src/time.c
index a72293e..67ad7d8 100644
--- a/lib/src/time.c
+++ b/lib/src/time.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
diff --git a/lib/src/utils.h b/lib/src/utils.h
index 0bd86a7..9f96d73 100644
--- a/lib/src/utils.h
+++ b/lib/src/utils.h
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_UTILS_H
#define CHIAKI_UTILS_H
diff --git a/lib/src/videoreceiver.c b/lib/src/videoreceiver.c
index fb00f8e..6468fcd 100644
--- a/lib/src/videoreceiver.c
+++ b/lib/src/videoreceiver.c
@@ -1,19 +1,4 @@
-/*
- * This file is part of Chiaki.
- *
- * Chiaki is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Chiaki is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Chiaki. If not, see .
- */
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#include
#include
@@ -22,7 +7,7 @@
static ChiakiErrorCode chiaki_video_receiver_flush_frame(ChiakiVideoReceiver *video_receiver);
-CHIAKI_EXPORT void chiaki_video_receiver_init(ChiakiVideoReceiver *video_receiver, struct chiaki_session_t *session)
+CHIAKI_EXPORT void chiaki_video_receiver_init(ChiakiVideoReceiver *video_receiver, struct chiaki_session_t *session, ChiakiPacketStats *packet_stats)
{
video_receiver->session = session;
video_receiver->log = session->log;
@@ -32,8 +17,10 @@ CHIAKI_EXPORT void chiaki_video_receiver_init(ChiakiVideoReceiver *video_receive
video_receiver->frame_index_cur = -1;
video_receiver->frame_index_prev = -1;
+ video_receiver->frame_index_prev_complete = 0;
chiaki_frame_processor_init(&video_receiver->frame_processor, video_receiver->log);
+ video_receiver->packet_stats = packet_stats;
}
CHIAKI_EXPORT void chiaki_video_receiver_fini(ChiakiVideoReceiver *video_receiver)
@@ -96,6 +83,9 @@ CHIAKI_EXPORT void chiaki_video_receiver_av_packet(ChiakiVideoReceiver *video_re
if(video_receiver->frame_index_cur < 0 ||
chiaki_seq_num_16_gt(frame_index, (ChiakiSeqNum16)video_receiver->frame_index_cur))
{
+ if(video_receiver->packet_stats)
+ chiaki_frame_processor_report_packet_stats(&video_receiver->frame_processor, video_receiver->packet_stats);
+
// last frame not flushed yet?
if(video_receiver->frame_index_cur >= 0 && video_receiver->frame_index_prev != video_receiver->frame_index_cur)
chiaki_video_receiver_flush_frame(video_receiver);
@@ -112,11 +102,11 @@ CHIAKI_EXPORT void chiaki_video_receiver_av_packet(ChiakiVideoReceiver *video_re
chiaki_frame_processor_alloc_frame(&video_receiver->frame_processor, packet);
}
+ chiaki_frame_processor_put_unit(&video_receiver->frame_processor, packet);
+
// if we are currently building up a frame
if(video_receiver->frame_index_cur != video_receiver->frame_index_prev)
{
- chiaki_frame_processor_put_unit(&video_receiver->frame_processor, packet);
-
// if we already have enough for the whole frame, flush it already
if(chiaki_frame_processor_flush_possible(&video_receiver->frame_processor))
chiaki_video_receiver_flush_frame(video_receiver);
@@ -161,4 +151,4 @@ static ChiakiErrorCode chiaki_video_receiver_flush_frame(ChiakiVideoReceiver *vi
video_receiver->frame_index_prev_complete = video_receiver->frame_index_cur;
return CHIAKI_ERR_SUCCESS;
-}
\ No newline at end of file
+}
diff --git a/scripts/Dockerfile b/scripts/Dockerfile
deleted file mode 100644
index 124a975..0000000
--- a/scripts/Dockerfile
+++ /dev/null
@@ -1,55 +0,0 @@
-# Run chiaki in a container
-# xhost +"local:docker@"
-# docker run -v $HOME/chiaki:/home/ps4/.local/share/Chiaki \
-# -v /tmp/.X11-unix:/tmp/.X11-unix \
-# --net host \
-# -e DISPLAY=$DISPLAY \
-# --device /dev/snd \
-# --name chiaki
-# --rm \
-# strubbl_chiaki
-
-FROM alpine:3 AS builder
-LABEL maintainer="Strubbl "
-WORKDIR /app
-RUN echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
- && apk update \
- && apk --no-cache add \
- build-base \
- cmake \
- ffmpeg-dev \
- git \
- openssl \
- opus-dev \
- protobuf \
- py3-protobuf@testing \
- qt5-qtbase \
- qt5-qtmultimedia-dev \
- qt5-qtsvg-dev \
- sdl2-dev \
- && git clone https://github.com/thestr4ng3r/chiaki.git /app \
- && git submodule update --init \
- && mkdir build \
- && cd build \
- && cmake .. \
- && make \
- && ./test/chiaki-unit \
- && rm -rf /var/cache/apk/*
-
-FROM alpine:3
-RUN apk update \
- && apk --no-cache add \
- ffmpeg \
- qt5-qtbase \
- qt5-qtmultimedia \
- qt5-qtsvg \
- sdl2 \
- && rm -rf /var/cache/apk/* \
- && addgroup ps4 && adduser -D -G ps4 ps4 \
- && chown -R ps4: /home/ps4
-WORKDIR /home/ps4
-COPY --from=builder /app/build/gui/chiaki .
-USER ps4
-VOLUME /home/ps4/.local/share/Chiaki
-CMD ["/home/ps4/chiaki"]
-
diff --git a/scripts/Dockerfile.bullseye b/scripts/Dockerfile.bullseye
new file mode 100644
index 0000000..888f3af
--- /dev/null
+++ b/scripts/Dockerfile.bullseye
@@ -0,0 +1,12 @@
+
+FROM debian:bullseye
+
+RUN apt-get update
+RUN apt-get -y install git g++ cmake ninja-build pkg-config \
+ libjerasure-dev nanopb libnanopb-dev libavcodec-dev libopus-dev \
+ libssl-dev protobuf-compiler python3 python3-protobuf \
+ libevdev-dev libudev-dev \
+ libqt5opengl5-dev libqt5svg5-dev qtmultimedia5-dev libsdl2-dev
+
+CMD []
+
diff --git a/scripts/Dockerfile.noble b/scripts/Dockerfile.noble
new file mode 100644
index 0000000..abc33c3
--- /dev/null
+++ b/scripts/Dockerfile.noble
@@ -0,0 +1,13 @@
+
+FROM ubuntu:noble
+
+RUN apt-get update
+# Hint: python3-setuptools should not be needed with nanopb >= 0.4.9
+RUN apt-get -y install git g++ cmake ninja-build curl pkg-config unzip \
+ python3-protobuf protobuf-compiler \
+ python3-setuptools \
+ libssl-dev libopus-dev qtbase5-dev qtmultimedia5-dev libqt5multimedia5-plugins libqt5svg5-dev \
+ libgl1-mesa-dev nasm libudev-dev libva-dev fuse libevdev-dev libudev-dev file
+
+CMD []
+
diff --git a/scripts/Dockerfile.ubuntu b/scripts/Dockerfile.ubuntu
deleted file mode 100644
index 9c862ee..0000000
--- a/scripts/Dockerfile.ubuntu
+++ /dev/null
@@ -1,57 +0,0 @@
-# Run chiaki in a container
-# docker run -v $HOME/chiaki:/home/ps4/.local/share/Chiaki \
-# -v /tmp/.X11-unix:/tmp/.X11-unix \
-# --net host \
-# -e DISPLAY=$DISPLAY \
-# --device /dev/snd \
-# --name chiaki
-# --rm \
-# strubbl_chiaki
-
-FROM ubuntu:19.04 AS builder
-LABEL maintainer="Strubbl "
-ENV DEBIAN_FRONTEND noninteractive
-WORKDIR /app
-RUN apt-get update \
- && apt-get install -y \
- build-essential \
- cmake \
- git \
- protobuf-compiler \
- libavcodec-dev \
- libavutil-dev \
- libopus-dev \
- libqt5svg5-dev \
- libsdl2-dev \
- libssl-dev \
- python3 \
- python3-distutils \
- python3-protobuf \
- qt5-default \
- qtmultimedia5-dev \
- && git clone https://github.com/thestr4ng3r/chiaki.git /app \
- && git submodule update --init \
- && mkdir build \
- && cd build \
- && cmake .. \
- && make
-
-FROM ubuntu:19.04
-ENV DEBIAN_FRONTEND noninteractive
-RUN apt-get update \
- && apt-get install -y \
- libavcodec58 \
- libavutil56 \
- libopus0 \
- libqt5multimedia5 \
- libqt5svg5 \
- libsdl2-2.0-0 \
- --no-install-recommends \
- && rm -rf /var/lib/apt/lists/* \
- && useradd -ms /bin/bash ps4
-WORKDIR /home/ps4
-USER ps4
-COPY --from=builder /app/build/gui/chiaki .
-VOLUME /home/ps4/.local/share/Chiaki
-ENTRYPOINT ["/home/ps4/chiaki"]
-
diff --git a/scripts/appveyor-win.sh b/scripts/appveyor-win.sh
new file mode 100755
index 0000000..7bbc0f3
--- /dev/null
+++ b/scripts/appveyor-win.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+set -xe
+
+BUILD_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
+BUILD_ROOT="$(echo $BUILD_ROOT | sed 's|^/\([a-z]\)|\1:|g')" # replace /c/... by c:/... for cmake to understand it
+echo "BUILD_ROOT=$BUILD_ROOT"
+
+mkdir ninja && cd ninja
+wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip && 7z x ninja-win.zip
+cd ..
+
+mkdir yasm && cd yasm
+wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe && mv yasm-1.3.0-win64.exe yasm.exe
+cd ..
+
+export PATH="$PWD/ninja:$PWD/yasm:/c/Qt/5.12/msvc2017_64/bin:$PATH"
+
+scripts/build-ffmpeg.sh . --target-os=win64 --arch=x86_64 --toolchain=msvc
+
+git clone https://github.com/xiph/opus.git && cd opus && git checkout ad8fe90db79b7d2a135e3dfd2ed6631b0c5662ab
+mkdir build && cd build
+cmake \
+ -G Ninja \
+ -DCMAKE_C_COMPILER=cl \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$BUILD_ROOT/opus-prefix" \
+ ..
+ninja
+ninja install
+cd ../..
+
+wget https://download.firedaemon.com/FireDaemon-OpenSSL/openssl-1.1.1s.zip && 7z x openssl-1.1.*.zip
+
+wget https://www.libsdl.org/release/SDL2-devel-2.26.2-VC.zip && 7z x SDL2-devel-2.26.2-VC.zip
+export SDL_ROOT="$BUILD_ROOT/SDL2-2.26.2"
+export SDL_ROOT=${SDL_ROOT//[\\]//}
+echo "set(SDL2_INCLUDE_DIRS \"$SDL_ROOT/include\")
+set(SDL2_LIBRARIES \"$SDL_ROOT/lib/x64/SDL2.lib\")
+set(SDL2_LIBDIR \"$SDL_ROOT/lib/x64\")
+include($SDL_ROOT/cmake/sdl2-config-version.cmake)" > "$SDL_ROOT/SDL2Config.cmake"
+
+mkdir protoc && cd protoc
+wget https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-win64.zip && 7z x protoc-3.9.1-win64.zip
+cd ..
+export PATH="$PWD/protoc/bin:$PATH"
+
+PYTHON="C:/Python37/python.exe"
+"$PYTHON" -m pip install protobuf==3.19.5
+
+QT_PATH="C:/Qt/5.15/msvc2019_64"
+
+COPY_DLLS="$PWD/openssl-1.1/x64/bin/libcrypto-1_1-x64.dll $PWD/openssl-1.1/x64/bin/libssl-1_1-x64.dll $SDL_ROOT/lib/x64/SDL2.dll"
+
+echo "-- Configure"
+
+mkdir build && cd build
+
+
+cmake \
+ -G Ninja \
+ -DCMAKE_C_COMPILER=cl \
+ -DCMAKE_C_FLAGS="-we4013" \
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DCMAKE_PREFIX_PATH="$BUILD_ROOT/ffmpeg-prefix;$BUILD_ROOT/opus-prefix;$BUILD_ROOT/openssl-1.1/x64;$QT_PATH;$SDL_ROOT" \
+ -DPYTHON_EXECUTABLE="$PYTHON" \
+ -DCHIAKI_ENABLE_TESTS=ON \
+ -DCHIAKI_ENABLE_CLI=OFF \
+ -DCHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER=ON \
+ ..
+
+echo "-- Build"
+
+ninja
+
+echo "-- Test"
+
+cp $COPY_DLLS test/
+test/chiaki-unit.exe
+
+cd ..
+
+
+# Deploy
+
+echo "-- Deploy"
+
+mkdir Chiaki && cp build/gui/chiaki.exe Chiaki
+mkdir Chiaki-PDB && cp build/gui/chiaki.pdb Chiaki-PDB
+
+"$QT_PATH/bin/windeployqt.exe" Chiaki/chiaki.exe
+cp -v $COPY_DLLS Chiaki
diff --git a/scripts/appveyor.sh b/scripts/appveyor.sh
deleted file mode 100755
index bec5c31..0000000
--- a/scripts/appveyor.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash
-
-echo "APPVEYOR_BUILD_FOLDER=$APPVEYOR_BUILD_FOLDER"
-
-mkdir ninja && cd ninja || exit 1
-wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip && 7z x ninja-win.zip || exit 1
-cd .. || exit 1
-
-mkdir yasm && cd yasm || exit 1
-wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe && mv yasm-1.3.0-win64.exe yasm.exe || exit 1
-cd .. || exit 1
-
-export PATH="$PWD/ninja:$PWD/yasm:/c/Qt/5.12/msvc2017_64/bin:$PATH"
-
-scripts/build-ffmpeg.sh --target-os=win64 --arch=x86_64 --toolchain=msvc || exit 1
-
-git clone https://github.com/xiph/opus.git && cd opus && git checkout ad8fe90db79b7d2a135e3dfd2ed6631b0c5662ab || exit 1
-mkdir build && cd build || exit 1
-cmake \
- -G Ninja \
- -DCMAKE_C_COMPILER=cl \
- -DCMAKE_BUILD_TYPE=Release \
- -DCMAKE_INSTALL_PREFIX="$APPVEYOR_BUILD_FOLDER/opus-prefix" \
- .. || exit 1
-ninja || exit 1
-ninja install || exit 1
-cd ../.. || exit 1
-
-wget https://mirror.firedaemon.com/OpenSSL/openssl-1.1.1d-dev.zip && 7z x openssl-1.1.1d-dev.zip || exit 1
-
-wget https://www.libsdl.org/release/SDL2-devel-2.0.10-VC.zip && 7z x SDL2-devel-2.0.10-VC.zip || exit 1
-export SDL_ROOT="$APPVEYOR_BUILD_FOLDER/SDL2-2.0.10" || exit 1
-export SDL_ROOT=${SDL_ROOT//[\\]//} || exit 1
-echo "set(SDL2_INCLUDE_DIRS \"$SDL_ROOT/include\")
-set(SDL2_LIBRARIES \"$SDL_ROOT/lib/x64/SDL2.lib\")
-set(SDL2_LIBDIR \"$SDL_ROOT/lib/x64\")" > "$SDL_ROOT/SDL2Config.cmake" || exit 1
-
-mkdir protoc && cd protoc || exit 1
-wget https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-win64.zip && 7z x protoc-3.9.1-win64.zip || exit 1
-cd .. || exit 1
-export PATH="$PWD/protoc/bin:$PATH" || exit 1
-
-PYTHON="C:/Python37/python.exe"
-"$PYTHON" -m pip install protobuf || exit 1
-
-QT_PATH="C:/Qt/5.12.4/msvc2017_64"
-
-COPY_DLLS="$PWD/openssl-1.1/x64/bin/libcrypto-1_1-x64.dll $PWD/openssl-1.1/x64/bin/libssl-1_1-x64.dll $SDL_ROOT/lib/x64/SDL2.dll"
-
-mkdir build && cd build || exit 1
-
-cmake \
- -G Ninja \
- -DCMAKE_C_COMPILER=cl \
- -DCMAKE_C_FLAGS="-we4013" \
- -DCMAKE_BUILD_TYPE=RelWithDebInfo \
- -DCMAKE_PREFIX_PATH="$APPVEYOR_BUILD_FOLDER/ffmpeg-prefix;$APPVEYOR_BUILD_FOLDER/opus-prefix;$APPVEYOR_BUILD_FOLDER/openssl-1.1/x64;$QT_PATH;$SDL_ROOT" \
- -DPYTHON_EXECUTABLE="$PYTHON" \
- -DCHIAKI_ENABLE_TESTS=ON \
- -DCHIAKI_ENABLE_CLI=OFF \
- -DCHIAKI_GUI_ENABLE_QT_GAMEPAD=OFF \
- -DCHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER=ON \
- .. || exit 1
-
-ninja || exit 1
-
-test/chiaki-unit.exe || exit 1
-
-cd .. || exit 1
-
-
-# Deploy
-
-mkdir Chiaki && cp build/gui/chiaki.exe Chiaki || exit 1
-mkdir Chiaki-PDB && cp build/gui/chiaki.pdb Chiaki-PDB || exit 1
-
-"$QT_PATH/bin/windeployqt.exe" Chiaki/chiaki.exe || exit 1
-cp -v $COPY_DLLS Chiaki
diff --git a/scripts/build-appimage.sh b/scripts/build-appimage.sh
new file mode 100755
index 0000000..05c0428
--- /dev/null
+++ b/scripts/build-appimage.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+set -xe
+
+# sometimes there are errors in linuxdeploy in docker/podman when the appdir is on a mount
+appdir=${1:-`pwd`/appimage/appdir}
+
+mkdir appimage
+
+export PATH="`pwd`/appimage/protoc/bin:$PATH"
+scripts/build-ffmpeg.sh appimage
+scripts/build-sdl2.sh appimage
+
+mkdir build_appimage
+cd build_appimage
+cmake \
+ -GNinja \
+ -DCMAKE_BUILD_TYPE=Release \
+ "-DCMAKE_PREFIX_PATH=`pwd`/../appimage/ffmpeg-prefix;`pwd`/../appimage/sdl2-prefix" \
+ -DCHIAKI_ENABLE_TESTS=ON \
+ -DCHIAKI_ENABLE_CLI=OFF \
+ -DCHIAKI_ENABLE_GUI=ON \
+ -DCHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER=ON \
+ -DCMAKE_INSTALL_PREFIX=/usr \
+ ..
+cd ..
+
+# purge leftover proto/nanopb_pb2.py which may have been created with another protobuf version
+rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py
+
+ninja -C build_appimage
+build_appimage/test/chiaki-unit
+
+DESTDIR="${appdir}" ninja -C build_appimage install
+cd appimage
+
+curl -L -O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+chmod +x linuxdeploy-x86_64.AppImage
+curl -L -O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
+chmod +x linuxdeploy-plugin-qt-x86_64.AppImage
+
+export LD_LIBRARY_PATH="`pwd`/sdl2-prefix/lib:$LD_LIBRARY_PATH"
+export EXTRA_QT_PLUGINS=opengl
+
+./linuxdeploy-x86_64.AppImage --appdir="${appdir}" -e "${appdir}/usr/bin/chiaki" -d "${appdir}/usr/share/applications/chiaki.desktop" --plugin qt --output appimage
+mv Chiaki*-x86_64.AppImage Chiaki.AppImage
diff --git a/scripts/travis-build.sh b/scripts/build-common.sh
similarity index 60%
rename from scripts/travis-build.sh
rename to scripts/build-common.sh
index e1790dd..dd4be2c 100755
--- a/scripts/travis-build.sh
+++ b/scripts/build-common.sh
@@ -1,14 +1,17 @@
#!/bin/bash
+# purge leftover proto/nanopb_pb2.py which may have been created with another protobuf version
+rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py
+
mkdir build && cd build || exit 1
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH \
-DCHIAKI_ENABLE_TESTS=ON \
-DCHIAKI_ENABLE_CLI=OFF \
- -DCHIAKI_GUI_ENABLE_QT_GAMEPAD=OFF \
-DCHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER=ON \
$CMAKE_EXTRA_ARGS \
.. || exit 1
make -j4 || exit 1
-test/chiaki-unit || exit 1
\ No newline at end of file
+test/chiaki-unit || exit 1
+
diff --git a/scripts/build-ffmpeg.sh b/scripts/build-ffmpeg.sh
index db73be4..9b6348f 100755
--- a/scripts/build-ffmpeg.sh
+++ b/scripts/build-ffmpeg.sh
@@ -1,12 +1,14 @@
#!/bin/bash
cd $(dirname "${BASH_SOURCE[0]}")/..
+cd "./$1"
+shift
ROOT="`pwd`"
-TAG=n4.2
+TAG=n4.3.9
git clone https://git.ffmpeg.org/ffmpeg.git --depth 1 -b $TAG && cd ffmpeg || exit 1
-./configure --disable-all --enable-avcodec --enable-decoder=h264 --prefix="$ROOT/ffmpeg-prefix" "$@" || exit 1
+./configure --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_vaapi --enable-hwaccel=hevc_vaapi --prefix="$ROOT/ffmpeg-prefix" "$@" || exit 1
make -j4 || exit 1
-make install || exit 1
\ No newline at end of file
+make install || exit 1
diff --git a/scripts/build-sdl2.sh b/scripts/build-sdl2.sh
index 7382bca..716b486 100755
--- a/scripts/build-sdl2.sh
+++ b/scripts/build-sdl2.sh
@@ -1,15 +1,19 @@
#!/bin/bash
+set -xe
+
cd $(dirname "${BASH_SOURCE[0]}")/..
+cd "./$1"
ROOT="`pwd`"
-URL=https://www.libsdl.org/release/SDL2-2.0.10.tar.gz
-FILE=SDL2-2.0.10.tar.gz
-DIR=SDL2-2.0.10
+SDL_VER=2.26.1
+URL=https://www.libsdl.org/release/SDL2-${SDL_VER}.tar.gz
+FILE=SDL2-${SDL_VER}.tar.gz
+DIR=SDL2-${SDL_VER}
if [ ! -d "$DIR" ]; then
- wget "$URL" || exit 1
- tar -xf "$FILE" || exit 1
+ curl -L "$URL" -O
+ tar -xf "$FILE"
fi
cd "$DIR" || exit 1
@@ -18,14 +22,14 @@ mkdir -p build && cd build || exit 1
cmake \
-DCMAKE_INSTALL_PREFIX="$ROOT/sdl2-prefix" \
-DSDL_ATOMIC=OFF \
- -DSDL_AUDIO=OFF \
+ -DSDL_AUDIO=ON \
-DSDL_CPUINFO=OFF \
-DSDL_EVENTS=ON \
-DSDL_FILE=OFF \
-DSDL_FILESYSTEM=OFF \
-DSDL_HAPTIC=ON \
-DSDL_JOYSTICK=ON \
- -DSDL_LOADSO=OFF \
+ -DSDL_LOADSO=ON \
-DSDL_RENDER=OFF \
-DSDL_SHARED=ON \
-DSDL_STATIC=OFF \
@@ -33,9 +37,9 @@ cmake \
-DSDL_THREADS=ON \
-DSDL_TIMERS=OFF \
-DSDL_VIDEO=OFF \
- .. || exit 1
+ ..
# SDL_THREADS is not needed, but it doesn't compile without
-make -j4 || exit 1
-make install || exit 1
+make -j4
+make install
diff --git a/scripts/chiaki.rb.in b/scripts/chiaki.rb.in
deleted file mode 100644
index c9fd136..0000000
--- a/scripts/chiaki.rb.in
+++ /dev/null
@@ -1,16 +0,0 @@
-cask 'chiaki' do
- version '@CHIAKI_VERSION@'
- sha256 '@CHIAKI_DMG_SHA256@'
-
- url "https://github.com/thestr4ng3r/chiaki/releases/download/v#{version}/Chiaki-v#{version}-macOS-x86_64.dmg"
- appcast 'https://github.com/thestr4ng3r/chiaki/releases.atom'
- name 'Chiaki'
- homepage 'https://github.com/thestr4ng3r/chiaki'
-
- app 'Chiaki.app'
-
- zap trash: [
- '~/Library/Application Support/Chiaki',
- '~/Library/Preferences/com.chiaki.Chiaki.plist',
- ]
-end
diff --git a/scripts/configure-cask.cmake b/scripts/configure-cask.cmake
deleted file mode 100644
index 62a7dae..0000000
--- a/scripts/configure-cask.cmake
+++ /dev/null
@@ -1,11 +0,0 @@
-
-if(NOT CHIAKI_VERSION OR NOT CHIAKI_DMG OR NOT CHIAKI_CASK_OUT)
- message(FATAL_ERROR "CHIAKI_VERSION, CHIAKI_DMG and CHIAKI_CASK_OUT must be set.")
-endif()
-
-if(CHIAKI_VERSION MATCHES "^v([0-9].*)$")
- set(CHIAKI_VERSION "${CMAKE_MATCH_1}")
-endif()
-
-file(SHA256 "${CHIAKI_DMG}" CHIAKI_DMG_SHA256)
-configure_file("${CMAKE_CURRENT_LIST_DIR}/chiaki.rb.in" "${CHIAKI_CASK_OUT}")
diff --git a/scripts/docker-run.sh b/scripts/docker-run.sh
new file mode 100755
index 0000000..55ee2ea
--- /dev/null
+++ b/scripts/docker-run.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+docker run \
+ -v $HOME/.local/share/Chiaki:/home/ps4/.local/share/Chiaki \
+ -v $HOME/.config/Chiaki:/home/ps4/.config/Chiaki \
+ -v /tmp/.X11-unix:/tmp/.X11-unix \
+ --net host \
+ -e DISPLAY=$DISPLAY \
+ --device /dev/snd \
+ --name chiaki \
+ --rm \
+ thestr4ng3r/chiaki
+
diff --git a/scripts/fetch-protoc.sh b/scripts/fetch-protoc.sh
deleted file mode 100755
index 2dec889..0000000
--- a/scripts/fetch-protoc.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-cd $(dirname "${BASH_SOURCE[0]}")/..
-ROOT="`pwd`"
-
-URL=https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-linux-x86_64.zip
-
-wget "$URL" -O protoc.zip || exit 1
-unzip protoc.zip -d protoc || exit 1
-
diff --git a/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json b/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json
new file mode 100644
index 0000000..daab8a0
--- /dev/null
+++ b/scripts/flatpak/com.github.thestr4ng3r.Chiaki.json
@@ -0,0 +1,98 @@
+{
+ "app-id": "org.flatpak.Chiaki",
+ "runtime": "org.kde.Platform",
+ "runtime-version": "5.14",
+ "sdk": "org.kde.Sdk",
+ "command": "chiaki",
+ "rename-icon": "chiaki",
+ "rename-desktop-file": "chiaki.desktop",
+ "separate-locales": false,
+ "finish-args": [
+ "--share=network",
+ "--share=ipc",
+ "--socket=wayland",
+ "--socket=x11",
+ "--device=all",
+ "--device=dri",
+ "--allow=bluetooth",
+ "--socket=pulseaudio",
+ "--filesystem=home",
+ "--own-name=org.kde.*",
+ "--env=DBUS_FATAL_WARNINGS=0",
+ "--talk-name=org.freedesktop.ScreenSaver"
+ ],
+ "add-extensions": {
+ "org.freedesktop.Platform.ffmpeg-full": {
+ "directory": "lib/ffmpeg",
+ "add-ld-path": ".",
+ "version": "19.08",
+ "autodownload": true,
+ "autodelete": false
+ },
+ "org.freedesktop.Platform.VAAPI.Intel": {
+ "directory": "lib/intel-vaapi-driver",
+ "add-ld-path": "lib",
+ "version": "19.08"
+ }
+ },
+ "modules": [
+ {
+ "name": "protobuf-compilers",
+ "buildsystem": "simple",
+ "build-commands": [
+ "install -D protoc /app/bin/protoc"
+ ],
+ "sources": [
+ {
+ "type": "archive",
+ "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protoc-3.12.0-linux-x86_64.zip",
+ "sha256": "3af5f90ad973c36bdaf5c4bd0082cfdc8881593ddf530fc6aa1442ee3d7a4e4b"
+ }
+ ]
+ },
+ {
+ "name": "ffmpeg",
+ "cleanup": [ "/include", "/lib/pkgconfig", "/share/ffmpeg/examples" ],
+ "config-opts": [
+ "--enable-shared",
+ "--disable-static",
+ "--enable-gnutls",
+ "--disable-doc",
+ "--disable-programs",
+ "--disable-encoders",
+ "--disable-muxers",
+ "--enable-encoder=png"
+ ],
+ "sources": [{
+ "type": "archive",
+ "url": "https://ffmpeg.org/releases/ffmpeg-4.2.2.tar.xz",
+ "sha256": "cb754255ab0ee2ea5f66f8850e1bd6ad5cac1cd855d0a2f4990fb8c668b0d29c"
+ }]
+ },
+ {
+ "name": "pip-install",
+ "buildsystem": "simple",
+ "build-options": {
+ "build-args": [
+ "--share=network"
+ ]
+ },
+ "build-commands": [
+ "pip3 install --prefix=/app google&&pip3 install --prefix=/app protobuf"
+ ]
+ },
+ {
+ "name": "chiaki",
+ "buildsystem": "cmake",
+ "builddir": "true",
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://git.sr.ht/~thestr4ng3r/chiaki",
+ "tag": "v2.1.1",
+ "commit": "2257030adeb5c5a84380c78d34be4d783971663c"
+ }
+ ]
+ }
+ ]
+}
diff --git a/scripts/macos-dist-local.sh b/scripts/macos-dist-local.sh
new file mode 100755
index 0000000..d2ac740
--- /dev/null
+++ b/scripts/macos-dist-local.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Build Chiaki for macOS distribution using dependencies from MacPorts and custom ffmpeg
+
+set -xe
+cd $(dirname "${BASH_SOURCE[0]}")/..
+scripts/build-ffmpeg.sh
+export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix"
+scripts/build-common.sh
+cp -a build/gui/chiaki.app Chiaki.app
+/opt/local/libexec/qt5/bin/macdeployqt Chiaki.app
+
+# Remove all LC_RPATH load commands that have absolute paths of the build machine
+RPATHS=$(otool -l Chiaki.app/Contents/MacOS/chiaki | grep -A 2 LC_RPATH | grep 'path /' | awk '{print $2}')
+for p in ${RPATHS}; do install_name_tool -delete_rpath "$p" Chiaki.app/Contents/MacOS/chiaki; done
+
+# This may warn because we already ran macdeployqt above
+/opt/local/libexec/qt5/bin/macdeployqt Chiaki.app -dmg
diff --git a/scripts/psn-account-id.py b/scripts/psn-account-id.py
index 5c50cb7..590249d 100755
--- a/scripts/psn-account-id.py
+++ b/scripts/psn-account-id.py
@@ -3,15 +3,45 @@
import sys
if sys.version_info < (3, 0, 0):
- print("DO NOT use Python 2.\nEVER.\nhttps://pythonclock.org")
- exit(1)
+ print("DO NOT use Python 2.\nEVER.\nhttps://pythonclock.org")
+ exit(1)
+
+import platform
+oldexit = exit
+def exit(code):
+ if platform.system() == "Windows":
+ import atexit
+ input("Press Enter to exit.")
+ oldexit(code)
+
+if sys.stdout.encoding.lower() == "ascii":
+ import codecs
+ sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)
+ sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer)
try:
- import requests
+ import requests
except ImportError as e:
- print(e)
- print("\"requests\" is not available. Install it with pip whatever.")
- exit(1)
+ print(e)
+ if platform.system() == "Windows":
+ from distutils.util import strtobool
+ a = input("The requests module is not available. Should we try to install it automatically using pip? [y/n] ")
+ while True:
+ try:
+ a = strtobool(a)
+ break
+ except ValueError:
+ a = input("Please answer with y or n: ")
+ if a == 1:
+ import subprocess
+ subprocess.call([sys.executable, "-m", "pip", "install", "requests"])
+ else:
+ exit(1)
+ else:
+ print("\"requests\" is not available. Install it with pip or your distribution's package manager.")
+ exit(1)
+
+import requests
from urllib.parse import urlparse, parse_qs, quote, urljoin
import pprint
@@ -40,8 +70,8 @@ code_url_s = input("> ")
code_url = urlparse(code_url_s)
query = parse_qs(code_url.query)
if "code" not in query or len(query["code"]) == 0 or len(query["code"][0]) == 0:
- print("☠️ URL did not contain code parameter")
- exit(1)
+ print("☠️ URL did not contain code parameter")
+ exit(1)
code = query["code"][0]
print("🌏 Requesting OAuth Token")
@@ -50,38 +80,37 @@ api_auth = requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)
body = "grant_type=authorization_code&code={}&redirect_uri=https://remoteplay.dl.playstation.net/remoteplay/redirect&".format(code)
token_request = requests.post(TOKEN_URL,
- auth = api_auth,
- headers = { "Content-Type": "application/x-www-form-urlencoded" },
- data = body.encode("ascii"))
+ auth = api_auth,
+ headers = { "Content-Type": "application/x-www-form-urlencoded" },
+ data = body.encode("ascii"))
print("⚠️ WARNING: From this point on, output might contain sensitive information in some cases!")
if token_request.status_code != 200:
- print("☠️ Request failed with code {}:\n{}".format(token_request.status_code, token_request.text))
- exit(1)
+ print("☠️ Request failed with code {}:\n{}".format(token_request.status_code, token_request.text))
+ exit(1)
token_json = token_request.json()
if "access_token" not in token_json:
- print("☠️ \"access_token\" is missing in response JSON:\n{}".format(token_request.text))
- exit(1)
+ print("☠️ \"access_token\" is missing in response JSON:\n{}".format(token_request.text))
+ exit(1)
token = token_json["access_token"]
print("🌏 Requesting Account Info")
-account_request = requests.get(TOKEN_URL + "/" + quote(token),
- auth = api_auth)
+account_request = requests.get(TOKEN_URL + "/" + quote(token), auth = api_auth)
if account_request.status_code != 200:
- print("☠️ Request failed with code {}:\n{}".format(account_request.status_code, account_request.text))
- exit(1)
+ print("☠️ Request failed with code {}:\n{}".format(account_request.status_code, account_request.text))
+ exit(1)
account_info = account_request.json()
print("🥦 Received Account Info:")
pprint.pprint(account_info)
if "user_id" not in account_info:
- print("☠️ \"user_id\" is missing in response or not a string")
- exit(1)
+ print("☠️ \"user_id\" is missing in response or not a string")
+ exit(1)
user_id = int(account_info["user_id"])
user_id_base64 = base64.b64encode(user_id.to_bytes(8, "little")).decode()
@@ -89,4 +118,5 @@ user_id_base64 = base64.b64encode(user_id.to_bytes(8, "little")).decode()
print()
print("🍙 This is your AccountID:")
print(user_id_base64)
+exit(0)
diff --git a/scripts/run-podman-build-appimage.sh b/scripts/run-podman-build-appimage.sh
new file mode 100755
index 0000000..5024952
--- /dev/null
+++ b/scripts/run-podman-build-appimage.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -xe
+cd "`dirname $(readlink -f ${0})`"
+
+podman build --arch amd64 -t localhost/chiaki-noble . -f Dockerfile.noble
+cd ..
+podman run --rm \
+ --arch amd64 \
+ -v "`pwd`:/build/chiaki" \
+ -w "/build/chiaki" \
+ --device /dev/fuse \
+ --cap-add SYS_ADMIN \
+ -t localhost/chiaki-noble \
+ /bin/bash -c "scripts/build-appimage.sh /build/appdir"
+
diff --git a/scripts/run-podman-build-bullseye.sh b/scripts/run-podman-build-bullseye.sh
new file mode 100755
index 0000000..344a76e
--- /dev/null
+++ b/scripts/run-podman-build-bullseye.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -xe
+cd "`dirname $(readlink -f ${0})`"
+
+podman build -t chiaki-bullseye . -f Dockerfile.bullseye
+cd ..
+podman run --rm -v "`pwd`:/build" chiaki-bullseye /bin/bash -c "
+ cd /build &&
+ rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py &&
+ mkdir build_bullseye &&
+ cmake -Bbuild_bullseye -GNinja -DCHIAKI_USE_SYSTEM_JERASURE=ON -DCHIAKI_USE_SYSTEM_NANOPB=ON &&
+ ninja -C build_bullseye &&
+ ninja -C build_bullseye test"
+
diff --git a/scripts/switch/build.sh b/scripts/switch/build.sh
new file mode 100755
index 0000000..37b825d
--- /dev/null
+++ b/scripts/switch/build.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+set -xveo pipefail
+
+arg1=$1
+build="./build"
+if [ "$arg1" != "linux" ]; then
+ toolchain="cmake/switch.cmake"
+ build="./build_switch"
+fi
+
+SCRIPTDIR=$(dirname "$0")
+BASEDIR=$(realpath "${SCRIPTDIR}/../../")
+
+build_chiaki (){
+ pushd "${BASEDIR}"
+ #rm -rf ./build
+
+ # purge leftover proto/nanopb_pb2.py which may have been created with another protobuf version
+ rm -fv third-party/nanopb/generator/proto/nanopb_pb2.py
+
+ cmake -B "${build}" \
+ -GNinja \
+ -DCMAKE_TOOLCHAIN_FILE=${toolchain} \
+ -DCHIAKI_ENABLE_TESTS=OFF \
+ -DCHIAKI_ENABLE_CLI=OFF \
+ -DCHIAKI_ENABLE_GUI=OFF \
+ -DCHIAKI_ENABLE_ANDROID=OFF \
+ -DCHIAKI_ENABLE_BOREALIS=ON \
+ -DCHIAKI_LIB_ENABLE_MBEDTLS=ON \
+ # -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \
+ # -DCMAKE_FIND_DEBUG_MODE=ON
+
+ ninja -C "${build}"
+ popd
+}
+
+build_chiaki
+
diff --git a/scripts/switch/dev-container.sh b/scripts/switch/dev-container.sh
new file mode 100755
index 0000000..4cfedb3
--- /dev/null
+++ b/scripts/switch/dev-container.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+cd "`dirname $(readlink -f ${0})`/../.."
+
+podman run --rm \
+ -v "`pwd`:/build/chiaki" \
+ -w "/build/chiaki" \
+ -it \
+ quay.io/thestr4ng3r/chiaki-build-switch:v3 \
+ /bin/bash
diff --git a/scripts/switch/push-podman-build-chiaki.sh b/scripts/switch/push-podman-build-chiaki.sh
new file mode 100755
index 0000000..ac82029
--- /dev/null
+++ b/scripts/switch/push-podman-build-chiaki.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+cd "`dirname $(readlink -f ${0})`/../.."
+
+docker run \
+ -v "`pwd`:/build/chiaki" \
+ -w "/build/chiaki" \
+ -ti -p 28771:28771 \
+ --entrypoint /opt/devkitpro/tools/bin/nxlink \
+ thestr4ng3r/chiaki-build-switch \
+ "$@" -s /build/chiaki/build_switch/switch/chiaki.nro
+
diff --git a/scripts/switch/run-podman-build-chiaki.sh b/scripts/switch/run-podman-build-chiaki.sh
new file mode 100755
index 0000000..513cdc8
--- /dev/null
+++ b/scripts/switch/run-podman-build-chiaki.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+cd "`dirname $(readlink -f ${0})`/../.."
+
+podman run --rm \
+ -v "`pwd`:/build/chiaki" \
+ -w "/build/chiaki" \
+ -it \
+ quay.io/thestr4ng3r/chiaki-build-switch:v3 \
+ ${1:-/bin/bash -c "scripts/switch/build.sh"}
diff --git a/scripts/travis-appimage.sh b/scripts/travis-appimage.sh
deleted file mode 100644
index 6950e78..0000000
--- a/scripts/travis-appimage.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-cd build && make install DESTDIR=../appdir && cd .. || exit 1
-wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage || exit 1
-wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage && chmod +x linuxdeploy-plugin-qt-x86_64.AppImage || exit 1
-source /opt/qt512/bin/qt512-env.sh || exit 1
-
-if [ -n "$SDL2_FROM_SRC" ]; then
- export LD_LIBRARY_PATH="$TRAVIS_BUILD_DIR/sdl2-prefix/lib:$LD_LIBRARY_PATH" || exit 1
-fi
-
-export EXTRA_QT_PLUGINS=opengl
-
-./linuxdeploy-x86_64.AppImage --appdir=appdir -e appdir/usr/bin/chiaki -d appdir/usr/share/applications/chiaki.desktop --plugin qt --output appimage || exit 1
-export DEPLOY_FILE="Chiaki-${CHIAKI_VERSION}-Linux-x86_64.AppImage" || exit 1
-mv Chiaki-*-x86_64.AppImage "$DEPLOY_FILE" || exit 1
\ No newline at end of file
diff --git a/secret.tar.enc b/secret.tar.enc
deleted file mode 100644
index eff9118..0000000
Binary files a/secret.tar.enc and /dev/null differ
diff --git a/setsu/CMakeLists.txt b/setsu/CMakeLists.txt
new file mode 100644
index 0000000..2dedb0c
--- /dev/null
+++ b/setsu/CMakeLists.txt
@@ -0,0 +1,26 @@
+
+cmake_minimum_required(VERSION 3.2)
+
+project(libsetsu)
+
+option(SETSU_BUILD_DEMOS "Build testing executables for libsetsu" OFF)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+add_library(setsu
+ include/setsu.h
+ src/setsu.c)
+
+target_include_directories(setsu PUBLIC include)
+
+find_package(Udev REQUIRED)
+find_package(Evdev REQUIRED)
+target_link_libraries(setsu Udev::libudev Evdev::libevdev)
+
+if(SETSU_BUILD_DEMOS)
+ add_executable(setsu-demo-touchpad demo/touchpad.c)
+ target_link_libraries(setsu-demo-touchpad setsu)
+ add_executable(setsu-demo-motion demo/motion.c)
+ target_link_libraries(setsu-demo-motion setsu)
+endif()
+
diff --git a/setsu/cmake/FindEvdev.cmake b/setsu/cmake/FindEvdev.cmake
new file mode 100644
index 0000000..23f2515
--- /dev/null
+++ b/setsu/cmake/FindEvdev.cmake
@@ -0,0 +1,31 @@
+# Provides: Evdev::libevdev
+
+set(_prefix Evdev)
+set(_target "${_prefix}::libevdev")
+
+find_package(PkgConfig)
+if(PkgConfig_FOUND AND NOT TARGET ${_target})
+ if(CMAKE_VERSION VERSION_LESS "3.6")
+ pkg_check_modules("${_prefix}" libevdev)
+ else()
+ pkg_check_modules("${_prefix}" libevdev IMPORTED_TARGET)
+ endif()
+ if((TARGET PkgConfig::${_prefix}) AND (NOT CMAKE_VERSION VERSION_LESS "3.11.0"))
+ set_target_properties(PkgConfig::${_prefix} PROPERTIES IMPORTED_GLOBAL ON)
+ add_library(${_target} ALIAS PkgConfig::${_prefix})
+ elseif(${_prefix}_FOUND)
+ add_library(${_target} INTERFACE IMPORTED)
+ set_target_properties(${_target} PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${${_prefix}_INCLUDE_DIRS}")
+ set_target_properties(${_target} PROPERTIES
+ INTERFACE_LINK_LIBRARIES "${${_prefix}_LIBRARIES}")
+ endif()
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args("${_prefix}"
+ REQUIRED_VARS "${_prefix}_LIBRARIES"
+ VERSION_VAR "${_prefix}_VERSION")
+
+unset(_prefix)
+unset(_target)
diff --git a/setsu/cmake/FindUdev.cmake b/setsu/cmake/FindUdev.cmake
new file mode 100644
index 0000000..c9c8450
--- /dev/null
+++ b/setsu/cmake/FindUdev.cmake
@@ -0,0 +1,31 @@
+# Provides: Udev::libudev
+
+set(_prefix Udev)
+set(_target "${_prefix}::libudev")
+
+find_package(PkgConfig)
+if(PkgConfig_FOUND AND NOT TARGET ${_target})
+ if(CMAKE_VERSION VERSION_LESS "3.6")
+ pkg_check_modules("${_prefix}" libudev)
+ else()
+ pkg_check_modules("${_prefix}" libudev IMPORTED_TARGET)
+ endif()
+ if((TARGET PkgConfig::${_prefix}) AND (NOT CMAKE_VERSION VERSION_LESS "3.11.0"))
+ set_target_properties(PkgConfig::${_prefix} PROPERTIES IMPORTED_GLOBAL ON)
+ add_library(${_target} ALIAS PkgConfig::${_prefix})
+ elseif(${_prefix}_FOUND)
+ add_library(${_target} INTERFACE IMPORTED)
+ set_target_properties(${_target} PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${${_prefix}_INCLUDE_DIRS}")
+ set_target_properties(${_target} PROPERTIES
+ INTERFACE_LINK_LIBRARIES "${${_prefix}_LIBRARIES}")
+ endif()
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args("${_prefix}"
+ REQUIRED_VARS "${_prefix}_LIBRARIES"
+ VERSION_VAR "${_prefix}_VERSION")
+
+unset(_prefix)
+unset(_target)
diff --git a/setsu/demo/motion.c b/setsu/demo/motion.c
new file mode 100644
index 0000000..ebb5fdd
--- /dev/null
+++ b/setsu/demo/motion.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+Setsu *setsu;
+
+#define NAME_LEN 8
+const char * const names[] = {
+ "accel x ",
+ "accel y ",
+ "accel z ",
+ " gyro x ",
+ " gyro y ",
+ " gyro z "
+};
+union
+{
+ struct
+ {
+ float accel_x, accel_y, accel_z;
+ float gyro_x, gyro_y, gyro_z;
+ };
+ float v[6];
+} vals;
+uint32_t timestamp;
+bool dirty = false;
+bool log_mode;
+volatile bool should_quit;
+
+#define LOG(...) do { if(log_mode) fprintf(stderr, __VA_ARGS__); } while(0)
+
+void sigint(int s)
+{
+ should_quit = true;
+}
+
+#define BAR_LENGTH 100
+#define BAR_MAX 2.0f
+#define BAR_MAX_GYRO M_PI
+
+void print_state()
+{
+ char buf[6 * (1 + NAME_LEN + BAR_LENGTH) + 1];
+ size_t i = 0;
+ for(size_t b=0; b<6; b++)
+ {
+ buf[i++] = '\n';
+ memcpy(buf + i, names[b], NAME_LEN);
+ i += NAME_LEN;
+ buf[i++] = '[';
+ size_t max = BAR_LENGTH-2;
+ for(size_t bi=0; bi 2 ? BAR_MAX_GYRO : BAR_MAX) * (2.0f * (float)(x) / (float)max - 1.0f))
+ float cur = BAR_VAL(bi);
+ float prev = BAR_VAL((int)bi - 1);
+ if(prev < 0.0f && cur >= 0.0f)
+ {
+ buf[i++] = '|';
+ continue;
+ }
+ bool cov = ((vals.v[b] < 0.0f) == (cur < 0.0f)) && fabsf(vals.v[b]) > fabsf(cur);
+ float next = BAR_VAL(bi + 1);
+#define MARK_VAL (b > 2 ? 0.5f * M_PI : 1.0f)
+ bool mark = cur < -MARK_VAL && next >= -MARK_VAL || prev < MARK_VAL && cur >= MARK_VAL;
+ buf[i++] = cov ? (mark ? '#' : '=') : (mark ? '.' : ' ');
+#undef BAR_VAL
+ }
+ buf[i++] = ']';
+ }
+ buf[i++] = '\0';
+ assert(i == sizeof(buf));
+ printf("\033[2J%s", buf);
+ fflush(stdout);
+}
+
+void event(SetsuEvent *event, void *user)
+{
+ dirty = true;
+ switch(event->type)
+ {
+ case SETSU_EVENT_DEVICE_ADDED: {
+ if(event->dev_type != SETSU_DEVICE_TYPE_MOTION)
+ break;
+ SetsuDevice *dev = setsu_connect(setsu, event->path, SETSU_DEVICE_TYPE_MOTION);
+ LOG("Device added: %s, connect %s\n", event->path, dev ? "succeeded" : "FAILED!");
+ break;
+ }
+ case SETSU_EVENT_DEVICE_REMOVED:
+ if(event->dev_type != SETSU_DEVICE_TYPE_MOTION)
+ break;
+ LOG("Device removed: %s\n", event->path);
+ break;
+ case SETSU_EVENT_MOTION:
+ LOG("Motion: %f, %f, %f / %f, %f, %f / %u\n",
+ event->motion.accel_x, event->motion.accel_y, event->motion.accel_z,
+ event->motion.gyro_x, event->motion.gyro_y, event->motion.gyro_z,
+ (unsigned int)event->motion.timestamp);
+ vals.accel_x = event->motion.accel_x;
+ vals.accel_y = event->motion.accel_y;
+ vals.accel_z = event->motion.accel_z;
+ vals.gyro_x = event->motion.gyro_x;
+ vals.gyro_y = event->motion.gyro_y;
+ vals.gyro_z = event->motion.gyro_z;
+ timestamp = event->motion.timestamp;
+ dirty = true;
+ default:
+ break;
+ }
+}
+
+void usage(const char *prog)
+{
+ printf("usage: %s [-l]\n -l log mode\n", prog);
+ exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ log_mode = false;
+ if(argc == 2)
+ {
+ if(!strcmp(argv[1], "-l"))
+ log_mode = true;
+ else
+ usage(argv[0]);
+ }
+ else if(argc != 1)
+ usage(argv[0]);
+
+ setsu = setsu_new();
+ if(!setsu)
+ {
+ printf("Failed to init setsu\n");
+ return 1;
+ }
+
+ struct sigaction sa = {0};
+ sa.sa_handler = sigint;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGINT, &sa, NULL);
+
+ dirty = true;
+ while(!should_quit)
+ {
+ if(dirty && !log_mode)
+ print_state();
+ dirty = false;
+ setsu_poll(setsu, event, NULL);
+ }
+ setsu_free(setsu);
+ printf("\nさよなら!\n");
+ return 0;
+}
+
diff --git a/setsu/demo/touchpad.c b/setsu/demo/touchpad.c
new file mode 100644
index 0000000..b51d628
--- /dev/null
+++ b/setsu/demo/touchpad.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+Setsu *setsu;
+
+#define WIDTH 1920
+#define HEIGHT 942
+#define TOUCHES_MAX 8
+#define SCALE 16
+
+struct {
+ bool down;
+ unsigned int tracking_id;
+ unsigned int x, y;
+} touches[TOUCHES_MAX];
+
+bool dirty = false;
+bool log_mode;
+volatile bool should_quit;
+
+#define LOG(...) do { if(log_mode) fprintf(stderr, __VA_ARGS__); } while(0)
+
+void sigint(int s)
+{
+ should_quit = true;
+}
+
+void print_state()
+{
+ char buf[(1 + WIDTH/SCALE)*(HEIGHT/SCALE) + 1];
+ size_t i = 0;
+ for(size_t y=0; ytype)
+ {
+ case SETSU_EVENT_DEVICE_ADDED: {
+ if(event->dev_type != SETSU_DEVICE_TYPE_TOUCHPAD)
+ break;
+ SetsuDevice *dev = setsu_connect(setsu, event->path, SETSU_DEVICE_TYPE_TOUCHPAD);
+ LOG("Device added: %s, connect %s\n", event->path, dev ? "succeeded" : "FAILED!");
+ break;
+ }
+ case SETSU_EVENT_DEVICE_REMOVED:
+ if(event->dev_type != SETSU_DEVICE_TYPE_TOUCHPAD)
+ break;
+ LOG("Device removed: %s\n", event->path);
+ break;
+ case SETSU_EVENT_TOUCH_DOWN:
+ LOG("Down for %s, tracking id %d\n", setsu_device_get_path(event->dev), event->touch.tracking_id);
+ for(size_t i=0; itouch.tracking_id;
+ break;
+ }
+ }
+ break;
+ case SETSU_EVENT_TOUCH_POSITION:
+ case SETSU_EVENT_TOUCH_UP:
+ if(event->type == SETSU_EVENT_TOUCH_UP)
+ LOG("Up for %s, tracking id %d\n", setsu_device_get_path(event->dev), event->touch.tracking_id);
+ else
+ LOG("Position for %s, tracking id %d: %u, %u\n", setsu_device_get_path(event->dev),
+ event->touch.tracking_id, (unsigned int)event->touch.x, (unsigned int)event->touch.y);
+ for(size_t i=0; itouch.tracking_id)
+ {
+ switch(event->type)
+ {
+ case SETSU_EVENT_TOUCH_POSITION:
+ touches[i].x = event->touch.x;
+ touches[i].y = event->touch.y;
+ break;
+ case SETSU_EVENT_TOUCH_UP:
+ touches[i].down = false;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ break;
+ case SETSU_EVENT_BUTTON_DOWN:
+ case SETSU_EVENT_BUTTON_UP:
+ LOG("Button for %s: %llu %s\n", setsu_device_get_path(event->dev),
+ (unsigned long long)event->button, event->type == SETSU_EVENT_BUTTON_DOWN ? "down" : "up");
+ break;
+ default:
+ break;
+ }
+}
+
+void usage(const char *prog)
+{
+ printf("usage: %s [-l]\n -l log mode\n", prog);
+ exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ log_mode = false;
+ if(argc == 2)
+ {
+ if(!strcmp(argv[1], "-l"))
+ log_mode = true;
+ else
+ usage(argv[0]);
+ }
+ else if(argc != 1)
+ usage(argv[0]);
+
+ memset(touches, 0, sizeof(touches));
+ setsu = setsu_new();
+ if(!setsu)
+ {
+ printf("Failed to init setsu\n");
+ return 1;
+ }
+
+ struct sigaction sa = {0};
+ sa.sa_handler = sigint;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGINT, &sa, NULL);
+
+ dirty = true;
+ while(!should_quit)
+ {
+ if(dirty && !log_mode)
+ print_state();
+ dirty = false;
+ setsu_poll(setsu, event, NULL);
+ }
+ setsu_free(setsu);
+ printf("\nさよなら!\n");
+ return 0;
+}
+
diff --git a/setsu/include/setsu.h b/setsu/include/setsu.h
new file mode 100644
index 0000000..a3ed0e3
--- /dev/null
+++ b/setsu/include/setsu.h
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef _SETSU_H
+#define _SETSU_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct setsu_t Setsu;
+typedef struct setsu_device_t SetsuDevice;
+typedef int SetsuTrackingId;
+
+typedef enum {
+ SETSU_DEVICE_TYPE_TOUCHPAD,
+ SETSU_DEVICE_TYPE_MOTION
+} SetsuDeviceType;
+
+typedef enum {
+ /* New device available to connect.
+ * Event will have path and type set to the new device. */
+ SETSU_EVENT_DEVICE_ADDED,
+
+ /* Previously available device removed.
+ * Event will have path and type set to the removed device.
+ * Any SetsuDevice connected to this path will automatically
+ * be disconnected and their pointers will be invalid immediately
+ * after the callback for this event returns. */
+ SETSU_EVENT_DEVICE_REMOVED,
+
+ /* Touch down.
+ * Event will have dev and tracking_id set. */
+ SETSU_EVENT_TOUCH_DOWN,
+
+ /* Touch down.
+ * Event will have dev and tracking_id set. */
+ SETSU_EVENT_TOUCH_UP,
+
+ /* Touch position update.
+ * Event will have dev, tracking_id, x and y set. */
+ SETSU_EVENT_TOUCH_POSITION,
+
+ /* Event will have dev and button set. */
+ SETSU_EVENT_BUTTON_DOWN,
+
+ /* Event will have dev and button set. */
+ SETSU_EVENT_BUTTON_UP,
+
+ /* Event will have motion set. */
+ SETSU_EVENT_MOTION
+} SetsuEventType;
+
+#define SETSU_BUTTON_0 (1u << 0)
+
+typedef uint64_t SetsuButton;
+
+typedef struct setsu_event_t
+{
+ SetsuEventType type;
+ union
+ {
+ struct
+ {
+ const char *path;
+ SetsuDeviceType dev_type;
+ };
+ struct
+ {
+ SetsuDevice *dev;
+ union
+ {
+ struct
+ {
+ SetsuTrackingId tracking_id;
+ uint32_t x, y;
+ } touch;
+ SetsuButton button;
+ struct
+ {
+ float accel_x, accel_y, accel_z; // unit is 1G
+ float gyro_x, gyro_y, gyro_z; // unit is rad/sec
+ uint32_t timestamp; // microseconds
+ } motion;
+ };
+ };
+ };
+} SetsuEvent;
+
+typedef void (*SetsuEventCb)(SetsuEvent *event, void *user);
+
+Setsu *setsu_new();
+void setsu_free(Setsu *setsu);
+void setsu_poll(Setsu *setsu, SetsuEventCb cb, void *user);
+SetsuDevice *setsu_connect(Setsu *setsu, const char *path, SetsuDeviceType type);
+void setsu_disconnect(Setsu *setsu, SetsuDevice *dev);
+const char *setsu_device_get_path(SetsuDevice *dev);
+uint32_t setsu_device_touchpad_get_width(SetsuDevice *dev);
+uint32_t setsu_device_touchpad_get_height(SetsuDevice *dev);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/setsu/src/setsu.c b/setsu/src/setsu.c
new file mode 100644
index 0000000..c31fe83
--- /dev/null
+++ b/setsu/src/setsu.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#define ENABLE_LOG
+
+#ifdef ENABLE_LOG
+#define SETSU_LOG(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define SETSU_LOG(...) do {} while(0)
+#endif
+
+#define DEG2RAD (2.0f * M_PI / 360.0f)
+
+typedef struct setsu_avail_device_t
+{
+ struct setsu_avail_device_t *next;
+ SetsuDeviceType type;
+ char *path;
+ bool connect_dirty; // whether the connect has not been sent as an event yet
+ bool disconnect_dirty; // whether the disconnect has not been sent as an event yet
+} SetsuAvailDevice;
+
+#define SLOTS_COUNT 16
+
+typedef struct setsu_device_t
+{
+ struct setsu_device_t *next;
+ char *path;
+ SetsuDeviceType type;
+ int fd;
+ struct libevdev *evdev;
+
+ union
+ {
+ struct
+ {
+ int min_x, min_y, max_x, max_y;
+
+ struct
+ {
+ /* Saves the old tracking id that was just up-ed.
+ * also for handling "atomic" up->down
+ * i.e. when there is an up, then down with a different tracking id
+ * in a single frame (before SYN_REPORT), this saves the old
+ * tracking id that must be reported as up. */
+ int tracking_id_prev;
+
+ int tracking_id;
+ int x, y;
+ bool downed;
+ bool pos_dirty;
+ } slots[SLOTS_COUNT];
+ unsigned int slot_cur;
+ uint64_t buttons_prev;
+ uint64_t buttons_cur;
+ } touchpad;
+ struct
+ {
+ int accel_res_x, accel_res_y, accel_res_z;
+ int gyro_res_x, gyro_res_y, gyro_res_z;
+ int accel_x, accel_y, accel_z;
+ int gyro_x, gyro_y, gyro_z;
+ uint32_t timestamp;
+ bool dirty;
+ } motion;
+ };
+} SetsuDevice;
+
+struct setsu_t
+{
+ struct udev *udev;
+ struct udev_monitor *udev_mon;
+ SetsuAvailDevice *avail_dev;
+ SetsuDevice *dev;
+};
+
+bool get_dev_ids(const char *path, uint32_t *vendor_id, uint32_t *model_id);
+static void scan_udev(Setsu *setsu);
+static void update_udev_device(Setsu *setsu, struct udev_device *dev);
+static SetsuDevice *connect(Setsu *setsu, const char *path);
+static void disconnect(Setsu *setsu, SetsuDevice *dev);
+static void poll_device(Setsu *setsu, SetsuDevice *dev, SetsuEventCb cb, void *user);
+static void device_event(Setsu *setsu, SetsuDevice *dev, struct input_event *ev, SetsuEventCb cb, void *user);
+static void device_drain(Setsu *setsu, SetsuDevice *dev, SetsuEventCb cb, void *user);
+
+Setsu *setsu_new()
+{
+ Setsu *setsu = calloc(1, sizeof(Setsu));
+ if(!setsu)
+ return NULL;
+
+ setsu->udev = udev_new();
+ if(!setsu->udev)
+ {
+ free(setsu);
+ return NULL;
+ }
+
+ setsu->udev_mon = udev_monitor_new_from_netlink(setsu->udev, "udev");
+ if(setsu->udev_mon)
+ {
+ udev_monitor_filter_add_match_subsystem_devtype(setsu->udev_mon, "input", NULL);
+ udev_monitor_enable_receiving(setsu->udev_mon);
+ }
+ else
+ SETSU_LOG("Failed to create udev monitor\n");
+
+ scan_udev(setsu);
+
+ return setsu;
+}
+
+void setsu_free(Setsu *setsu)
+{
+ if(!setsu)
+ return;
+ while(setsu->dev)
+ setsu_disconnect(setsu, setsu->dev);
+ if(setsu->udev_mon)
+ udev_monitor_unref(setsu->udev_mon);
+ udev_unref(setsu->udev);
+ while(setsu->avail_dev)
+ {
+ SetsuAvailDevice *adev = setsu->avail_dev;
+ setsu->avail_dev = adev->next;
+ free(adev->path);
+ free(adev);
+ }
+ free(setsu);
+}
+
+static void scan_udev(Setsu *setsu)
+{
+ struct udev_enumerate *udev_enum = udev_enumerate_new(setsu->udev);
+ if(!udev_enum)
+ return;
+
+ if(udev_enumerate_add_match_subsystem(udev_enum, "input") < 0)
+ goto beach;
+
+ //if(udev_enumerate_add_match_property(udev_enum, "ID_INPUT_TOUCHPAD", "1") < 0)
+ // goto beach;
+
+ if(udev_enumerate_scan_devices(udev_enum) < 0)
+ goto beach;
+
+ for(struct udev_list_entry *entry = udev_enumerate_get_list_entry(udev_enum); entry; entry = udev_list_entry_get_next(entry))
+ {
+ const char *path = udev_list_entry_get_name(entry);
+ if(!path)
+ continue;
+ struct udev_device *dev = udev_device_new_from_syspath(setsu->udev, path);
+ if(!dev)
+ continue;
+ update_udev_device(setsu, dev);
+ udev_device_unref(dev);
+ }
+
+beach:
+ udev_enumerate_unref(udev_enum);
+}
+
+static bool is_device_interesting(struct udev_device *dev, SetsuDeviceType *type)
+{
+ static const uint32_t device_ids[] = {
+ // vendor id, model id
+ 0x054c, 0x05c4, // DualShock 4 Gen 1
+ 0x054c, 0x09cc, // DualShock 4 Gen 2
+ 0x54c, 0x0ce6 // DualSense
+ };
+
+ const char *touchpad_str = udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD");
+ const char *accel_str = udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
+ if(touchpad_str && !strcmp(touchpad_str, "1"))
+ {
+ // Filter mouse-device (/dev/input/mouse*) away and only keep the evdev (/dev/input/event*) one:
+ if(!udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD_INTEGRATION"))
+ return false;
+ *type = SETSU_DEVICE_TYPE_TOUCHPAD;
+ }
+ else if(accel_str && !strcmp(accel_str, "1"))
+ {
+ // Filter /dev/input/js* away and keep /dev/input/event*
+ if(!udev_device_get_property_value(dev, "ID_INPUT_WIDTH_MM"))
+ return false;
+ *type = SETSU_DEVICE_TYPE_MOTION;
+ }
+ else
+ return false;
+
+ uint32_t vendor;
+ uint32_t model;
+ // Try to get the ids from udev first. This fails for bluetooth.
+ const char *vendor_str = udev_device_get_property_value(dev, "ID_VENDOR_ID");
+ const char *model_str = udev_device_get_property_value(dev, "ID_MODEL_ID");
+ if(vendor_str && model_str)
+ {
+ vendor = strtoul(vendor_str, NULL, 16);
+ model = strtoul(model_str, NULL, 16);
+ }
+ else
+ {
+ const char *path = udev_device_get_devnode(dev);
+ if(!path)
+ return false;
+ if(!get_dev_ids(path, &vendor, &model))
+ return false;
+ }
+
+ for(const uint32_t *dev_id = device_ids; (char *)dev_id != ((char *)device_ids) + sizeof(device_ids); dev_id += 2)
+ {
+ if(vendor == dev_id[0] && model == dev_id[1])
+ return true;
+ }
+ return false;
+}
+
+static void update_udev_device(Setsu *setsu, struct udev_device *dev)
+{
+ const char *path = udev_device_get_devnode(dev);
+ if(!path)
+ return;
+
+ const char *action = udev_device_get_action(dev);
+ bool added;
+ if(!action || !strcmp(action, "add"))
+ added = true;
+ else if(!strcmp(action, "remove"))
+ added = false;
+ else
+ return;
+
+ for(SetsuAvailDevice *adev = setsu->avail_dev; adev; adev=adev->next)
+ {
+ if(!strcmp(adev->path, path))
+ {
+ if(added)
+ return; // already added, do nothing
+ // disconnected
+ adev->disconnect_dirty = true;
+ return;
+ }
+ }
+
+ // not yet added
+ SetsuDeviceType type;
+ if(!is_device_interesting(dev, &type))
+ return;
+
+ SetsuAvailDevice *adev = calloc(1, sizeof(SetsuAvailDevice));
+ if(!adev)
+ return;
+ adev->type = type;
+ adev->path = strdup(path);
+ if(!adev->path)
+ {
+ free(adev);
+ return;
+ }
+ adev->next = setsu->avail_dev;
+ setsu->avail_dev = adev;
+ adev->connect_dirty = true;
+}
+
+static void poll_udev_monitor(Setsu *setsu)
+{
+ if(!setsu->udev_mon)
+ return;
+ while(1)
+ {
+ struct udev_device *dev = udev_monitor_receive_device(setsu->udev_mon);
+ if(!dev)
+ break;
+ update_udev_device(setsu, dev);
+ udev_device_unref(dev);
+ }
+}
+
+bool get_dev_ids(const char *path, uint32_t *vendor_id, uint32_t *model_id)
+{
+ int fd = open(path, O_RDONLY | O_NONBLOCK);
+ if(fd == -1)
+ return false;
+
+ struct libevdev *evdev;
+ if(libevdev_new_from_fd(fd, &evdev) < 0)
+ {
+ close(fd);
+ return false;
+ }
+
+ *vendor_id = (uint32_t)libevdev_get_id_vendor(evdev);
+ *model_id = (uint32_t)libevdev_get_id_product(evdev);
+ libevdev_free(evdev);
+ close(fd);
+ return true;
+}
+
+SetsuDevice *setsu_connect(Setsu *setsu, const char *path, SetsuDeviceType type)
+{
+ SetsuDevice *dev = calloc(1, sizeof(SetsuDevice));
+ if(!dev)
+ return NULL;
+ dev->fd = -1;
+ dev->path = strdup(path);
+ if(!dev->path)
+ goto error;
+ dev->type = type;
+
+ dev->fd = open(dev->path, O_RDONLY | O_NONBLOCK);
+ if(dev->fd == -1)
+ {
+ SETSU_LOG("Failed to open %s\n", dev->path);
+ perror("setsu_connect");
+ goto error;
+ }
+
+ if(libevdev_new_from_fd(dev->fd, &dev->evdev) < 0)
+ {
+ dev->evdev = NULL;
+ goto error;
+ }
+
+ switch(type)
+ {
+ case SETSU_DEVICE_TYPE_TOUCHPAD:
+ dev->touchpad.min_x = libevdev_get_abs_minimum(dev->evdev, ABS_X);
+ dev->touchpad.min_y = libevdev_get_abs_minimum(dev->evdev, ABS_Y);
+ dev->touchpad.max_x = libevdev_get_abs_maximum(dev->evdev, ABS_X);
+ dev->touchpad.max_y = libevdev_get_abs_maximum(dev->evdev, ABS_Y);
+
+ for(size_t i=0; itouchpad.slots[i].tracking_id_prev = -1;
+ dev->touchpad.slots[i].tracking_id = -1;
+ }
+ break;
+ case SETSU_DEVICE_TYPE_MOTION:
+ dev->motion.accel_res_x = libevdev_get_abs_resolution(dev->evdev, ABS_X);
+ dev->motion.accel_res_y = libevdev_get_abs_resolution(dev->evdev, ABS_Y);
+ dev->motion.accel_res_z = libevdev_get_abs_resolution(dev->evdev, ABS_Z);
+ dev->motion.gyro_res_x = libevdev_get_abs_resolution(dev->evdev, ABS_RX);
+ dev->motion.gyro_res_y = libevdev_get_abs_resolution(dev->evdev, ABS_RY);
+ dev->motion.gyro_res_z = libevdev_get_abs_resolution(dev->evdev, ABS_RZ);
+ dev->motion.accel_y = dev->motion.accel_res_y; // 1G down
+ break;
+ }
+
+ dev->next = setsu->dev;
+ setsu->dev = dev;
+ return dev;
+error:
+ if(dev->evdev)
+ libevdev_free(dev->evdev);
+ if(dev->fd != -1)
+ close(dev->fd);
+ free(dev->path);
+ free(dev);
+ return NULL;
+}
+
+void setsu_disconnect(Setsu *setsu, SetsuDevice *dev)
+{
+ if(setsu->dev == dev)
+ setsu->dev = dev->next;
+ else
+ {
+ for(SetsuDevice *pdev = setsu->dev; pdev; pdev = pdev->next)
+ {
+ if(pdev->next == dev)
+ {
+ pdev->next = dev->next;
+ break;
+ }
+ }
+ }
+ libevdev_free(dev->evdev);
+ close(dev->fd);
+ free(dev->path);
+ free(dev);
+}
+
+const char *setsu_device_get_path(SetsuDevice *dev)
+{
+ return dev->path;
+}
+
+uint32_t setsu_device_touchpad_get_width(SetsuDevice *dev)
+{
+ if(dev->type != SETSU_DEVICE_TYPE_TOUCHPAD)
+ return 0;
+ return dev->touchpad.max_x - dev->touchpad.min_x;
+}
+
+uint32_t setsu_device_touchpad_get_height(SetsuDevice *dev)
+{
+ if(dev->type != SETSU_DEVICE_TYPE_TOUCHPAD)
+ return 0;
+ return dev->touchpad.max_y - dev->touchpad.min_y;
+}
+
+void kill_avail_device(Setsu *setsu, SetsuAvailDevice *adev)
+{
+ for(SetsuDevice *dev = setsu->dev; dev;)
+ {
+ if(!strcmp(dev->path, adev->path))
+ {
+ SetsuDevice *next = dev->next;
+ setsu_disconnect(setsu, dev);
+ dev = next;
+ continue;
+ }
+ dev = dev->next;
+ }
+
+ if(setsu->avail_dev == adev)
+ setsu->avail_dev = adev->next;
+ else
+ {
+ for(SetsuAvailDevice *padev = setsu->avail_dev; padev; padev = padev->next)
+ {
+ if(padev->next == adev)
+ {
+ padev->next = adev->next;
+ break;
+ }
+ }
+ }
+ free(adev->path);
+ free(adev);
+}
+
+void setsu_poll(Setsu *setsu, SetsuEventCb cb, void *user)
+{
+ poll_udev_monitor(setsu);
+
+ for(SetsuAvailDevice *adev = setsu->avail_dev; adev;)
+ {
+ if(adev->connect_dirty)
+ {
+ SetsuEvent event = { 0 };
+ event.type = SETSU_EVENT_DEVICE_ADDED;
+ event.path = adev->path;
+ event.dev_type = adev->type;
+ cb(&event, user);
+ adev->connect_dirty = false;
+ }
+ if(adev->disconnect_dirty)
+ {
+ SetsuEvent event = { 0 };
+ event.type = SETSU_EVENT_DEVICE_REMOVED;
+ event.path = adev->path;
+ event.dev_type = adev->type;
+ cb(&event, user);
+ // kill the device only after sending the event
+ SetsuAvailDevice *next = adev->next;
+ kill_avail_device(setsu, adev);
+ adev = next;
+ continue;
+ }
+ adev = adev->next;
+ }
+
+ for(SetsuDevice *dev = setsu->dev; dev; dev = dev->next)
+ poll_device(setsu, dev, cb, user);
+}
+
+static void poll_device(Setsu *setsu, SetsuDevice *dev, SetsuEventCb cb, void *user)
+{
+ bool sync = false;
+ while(true)
+ {
+ struct input_event ev;
+ int r = libevdev_next_event(dev->evdev, sync ? LIBEVDEV_READ_FLAG_SYNC : LIBEVDEV_READ_FLAG_NORMAL, &ev);
+ if(r == -EAGAIN)
+ {
+ if(sync)
+ {
+ sync = false;
+ continue;
+ }
+ break;
+ }
+ if(r == LIBEVDEV_READ_STATUS_SUCCESS || (sync && r == LIBEVDEV_READ_STATUS_SYNC))
+ device_event(setsu, dev, &ev, cb, user);
+ else if(r == LIBEVDEV_READ_STATUS_SYNC)
+ sync = true;
+ else if(r == -ENODEV) { break; } // device probably disconnected, udev remove event should follow soon
+ else
+ {
+ char buf[256];
+ strerror_r(-r, buf, sizeof(buf));
+ SETSU_LOG("evdev poll failed: %s\n", buf);
+ break;
+ }
+ }
+}
+
+static uint64_t button_from_evdev(int key)
+{
+ switch(key)
+ {
+ case BTN_LEFT:
+ return SETSU_BUTTON_0;
+ default:
+ return 0;
+ }
+}
+
+static void device_event(Setsu *setsu, SetsuDevice *dev, struct input_event *ev, SetsuEventCb cb, void *user)
+{
+#if 0
+ SETSU_LOG("Event: %s %s %d\n",
+ libevdev_event_type_get_name(ev->type),
+ libevdev_event_code_get_name(ev->type, ev->code),
+ ev->value);
+#endif
+ if(ev->type == EV_SYN && ev->code == SYN_REPORT)
+ {
+ device_drain(setsu, dev, cb, user);
+ return;
+ }
+ switch(dev->type)
+ {
+ case SETSU_DEVICE_TYPE_TOUCHPAD:
+ switch(ev->type)
+ {
+ case EV_ABS:
+#define S dev->touchpad.slots[dev->touchpad.slot_cur]
+ switch(ev->code)
+ {
+ case ABS_MT_SLOT:
+ if((unsigned int)ev->value >= SLOTS_COUNT)
+ {
+ SETSU_LOG("slot too high\n");
+ break;
+ }
+ dev->touchpad.slot_cur = ev->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if(S.tracking_id != -1 && S.tracking_id_prev == -1)
+ {
+ // up the tracking id
+ S.tracking_id_prev = S.tracking_id;
+ // reset the rest
+ S.x = S.y = 0;
+ S.pos_dirty = false;
+ }
+ S.tracking_id = ev->value;
+ if(ev->value != -1)
+ S.downed = true;
+ break;
+ case ABS_MT_POSITION_X:
+ S.x = ev->value;
+ S.pos_dirty = true;
+ break;
+ case ABS_MT_POSITION_Y:
+ S.y = ev->value;
+ S.pos_dirty = true;
+ break;
+ }
+ break;
+#undef S
+ case EV_KEY: {
+ uint64_t button = button_from_evdev(ev->code);
+ if(!button)
+ break;
+ if(ev->value)
+ dev->touchpad.buttons_cur |= button;
+ else
+ dev->touchpad.buttons_cur &= ~button;
+ break;
+ }
+ }
+ break;
+ case SETSU_DEVICE_TYPE_MOTION:
+ switch(ev->type)
+ {
+ case EV_ABS:
+ switch(ev->code)
+ {
+ case ABS_X:
+ dev->motion.accel_x = ev->value;
+ dev->motion.dirty = true;
+ break;
+ case ABS_Y:
+ dev->motion.accel_y = ev->value;
+ dev->motion.dirty = true;
+ break;
+ case ABS_Z:
+ dev->motion.accel_z = ev->value;
+ dev->motion.dirty = true;
+ break;
+ case ABS_RX:
+ dev->motion.gyro_x = ev->value;
+ dev->motion.dirty = true;
+ break;
+ case ABS_RY:
+ dev->motion.gyro_y = ev->value;
+ dev->motion.dirty = true;
+ break;
+ case ABS_RZ:
+ dev->motion.gyro_z = ev->value;
+ dev->motion.dirty = true;
+ break;
+ }
+ break;
+ case EV_MSC:
+ if(ev->code == MSC_TIMESTAMP)
+ {
+ dev->motion.timestamp = ev->value;
+ dev->motion.dirty = true;
+ }
+ break;
+ }
+ break;
+ }
+}
+
+static void device_drain(Setsu *setsu, SetsuDevice *dev, SetsuEventCb cb, void *user)
+{
+ SetsuEvent event;
+#define BEGIN_EVENT(tp) do { memset(&event, 0, sizeof(event)); event.dev = dev; event.type = tp; } while(0)
+#define SEND_EVENT() do { cb(&event, user); } while (0)
+ switch(dev->type)
+ {
+ case SETSU_DEVICE_TYPE_TOUCHPAD:
+ for(size_t i=0; itouchpad.slots[i].tracking_id_prev != -1)
+ {
+ BEGIN_EVENT(SETSU_EVENT_TOUCH_UP);
+ event.touch.tracking_id = dev->touchpad.slots[i].tracking_id_prev;
+ SEND_EVENT();
+ dev->touchpad.slots[i].tracking_id_prev = -1;
+ }
+ if(dev->touchpad.slots[i].downed)
+ {
+ BEGIN_EVENT(SETSU_EVENT_TOUCH_DOWN);
+ event.touch.tracking_id = dev->touchpad.slots[i].tracking_id;
+ SEND_EVENT();
+ dev->touchpad.slots[i].downed = false;
+ }
+ if(dev->touchpad.slots[i].pos_dirty)
+ {
+ BEGIN_EVENT(SETSU_EVENT_TOUCH_POSITION);
+ event.touch.tracking_id = dev->touchpad.slots[i].tracking_id;
+ event.touch.x = (uint32_t)(dev->touchpad.slots[i].x - dev->touchpad.min_x);
+ event.touch.y = (uint32_t)(dev->touchpad.slots[i].y - dev->touchpad.min_y);
+ SEND_EVENT();
+ dev->touchpad.slots[i].pos_dirty = false;
+ }
+ }
+
+ uint64_t buttons_diff = dev->touchpad.buttons_prev ^ dev->touchpad.buttons_cur;
+ for(uint64_t i=0; i<64; i++)
+ {
+ if(buttons_diff & 1)
+ {
+ uint64_t button = 1 << i;
+ BEGIN_EVENT((dev->touchpad.buttons_cur & button) ? SETSU_EVENT_BUTTON_DOWN : SETSU_EVENT_BUTTON_UP);
+ event.button = button;
+ SEND_EVENT();
+ }
+ buttons_diff >>= 1;
+ if(!buttons_diff)
+ break;
+ }
+ dev->touchpad.buttons_prev = dev->touchpad.buttons_cur;
+ break;
+ case SETSU_DEVICE_TYPE_MOTION:
+ if(dev->motion.dirty)
+ {
+ BEGIN_EVENT(SETSU_EVENT_MOTION);
+ event.motion.accel_x = (float)dev->motion.accel_x / (float)dev->motion.accel_res_x;
+ event.motion.accel_y = (float)dev->motion.accel_y / (float)dev->motion.accel_res_y;
+ event.motion.accel_z = (float)dev->motion.accel_z / (float)dev->motion.accel_res_z;
+ event.motion.gyro_x = DEG2RAD * (float)dev->motion.gyro_x / (float)dev->motion.gyro_res_x;
+ event.motion.gyro_y = DEG2RAD * (float)dev->motion.gyro_y / (float)dev->motion.gyro_res_y;
+ event.motion.gyro_z = DEG2RAD * (float)dev->motion.gyro_z / (float)dev->motion.gyro_res_z;
+ event.motion.timestamp = dev->motion.timestamp;
+ SEND_EVENT();
+ dev->motion.dirty = false;
+ }
+ break;
+ }
+#undef BEGIN_EVENT
+#undef SEND_EVENT
+}
+
diff --git a/switch/CMakeLists.txt b/switch/CMakeLists.txt
new file mode 100644
index 0000000..3d5639a
--- /dev/null
+++ b/switch/CMakeLists.txt
@@ -0,0 +1,131 @@
+
+##################
+# borealis dependency
+##################
+
+# do not include
+# borealis/library/lib/switch_wrapper.c
+# switch functions are in switch/src/main.cpp
+set(BOREALIS_SOURCE
+ borealis/library/lib/extern/libretro-common/features/features_cpu.c
+ borealis/library/lib/extern/libretro-common/encodings/encoding_utf.c
+ borealis/library/lib/extern/libretro-common/compat/compat_strl.c
+ borealis/library/lib/extern/nxfmtwrapper/format.cpp
+ borealis/library/lib/extern/nanovg/nanovg.c
+ borealis/library/lib/extern/glad/glad.c
+ borealis/library/lib/scroll_view.cpp
+ borealis/library/lib/style.cpp
+ borealis/library/lib/table.cpp
+ borealis/library/lib/task_manager.cpp
+ borealis/library/lib/progress_display.cpp
+ borealis/library/lib/staged_applet_frame.cpp
+ borealis/library/lib/applet_frame.cpp
+ borealis/library/lib/hint.cpp
+ borealis/library/lib/image.cpp
+ borealis/library/lib/logger.cpp
+ borealis/library/lib/swkbd.cpp
+ borealis/library/lib/crash_frame.cpp
+ borealis/library/lib/header.cpp
+ borealis/library/lib/progress_spinner.cpp
+ borealis/library/lib/layer_view.cpp
+ borealis/library/lib/notification_manager.cpp
+ borealis/library/lib/rectangle.cpp
+ borealis/library/lib/application.cpp
+ borealis/library/lib/box_layout.cpp
+ borealis/library/lib/sidebar.cpp
+ borealis/library/lib/dropdown.cpp
+ borealis/library/lib/popup_frame.cpp
+ borealis/library/lib/repeating_task.cpp
+ borealis/library/lib/absolute_layout.cpp
+ borealis/library/lib/i18n.cpp
+ borealis/library/lib/tab_frame.cpp
+ borealis/library/lib/thumbnail_frame.cpp
+ borealis/library/lib/animations.cpp
+ borealis/library/lib/dialog.cpp
+ borealis/library/lib/view.cpp
+ borealis/library/lib/list.cpp
+ borealis/library/lib/button.cpp
+ borealis/library/lib/label.cpp
+ borealis/library/lib/theme.cpp
+ borealis/library/lib/material_icon.cpp)
+
+add_library(borealis STATIC ${BOREALIS_SOURCE})
+set_property(TARGET borealis PROPERTY CXX_STANDARD 17)
+target_include_directories(borealis PUBLIC
+ borealis/library/include
+ borealis/library/include/borealis/extern
+ borealis/library/include/borealis/extern/glad
+ borealis/library/include/borealis/extern/nanovg
+ borealis/library/include/borealis/extern/libretro-common
+ borealis/library/lib/extern/fmt/include)
+
+find_package(glfw3 REQUIRED)
+find_library(EGL EGL)
+target_link_libraries(borealis PUBLIC
+ glfw
+ ${EGL})
+
+if(CHIAKI_IS_SWITCH)
+ target_compile_definitions(borealis PUBLIC
+ BOREALIS_RESOURCES="romfs:/")
+ find_library(GLAPI glapi)
+ find_library(DRM_NOUVEAU drm_nouveau)
+ target_link_libraries(borealis PUBLIC ${GLAPI} ${DRM_NOUVEAU})
+else()
+ target_compile_definitions(borealis PUBLIC
+ BOREALIS_RESOURCES="./switch/res/")
+endif()
+
+
+##################
+# chiaki with borealis
+##################
+
+find_package(FFMPEG REQUIRED COMPONENTS avcodec avutil swscale)
+find_library(SDL2 SDL2)
+find_library(SWRESAMPLE swresample)
+
+# find -type f | grep -P '\.(h|cpp)$' | sed 's#\./#\t\t#g'
+add_executable(chiaki-borealis WIN32
+ src/discoverymanager.cpp
+ src/settings.cpp
+ src/io.cpp
+ src/host.cpp
+ src/main.cpp
+ src/gui.cpp)
+
+set_target_properties(chiaki-borealis PROPERTIES
+ CXX_STANDARD 17
+ OUTPUT_NAME chiaki)
+
+target_include_directories(chiaki-borealis PRIVATE include)
+
+target_link_libraries(chiaki-borealis
+ chiaki-lib
+ borealis
+ ${SDL2}
+ FFMPEG::avcodec
+ FFMPEG::avutil
+ FFMPEG::swscale
+ ${SWRESAMPLE}
+ ${SWSCALE})
+
+if(CHIAKI_IS_SWITCH)
+ # libnx is forced by the switch toolchain
+ find_library(Z z)
+ target_link_libraries(chiaki-borealis ${Z} ${GLAPI})
+endif()
+
+install(TARGETS chiaki-borealis
+ RUNTIME DESTINATION bin
+ BUNDLE DESTINATION bin)
+
+if(CHIAKI_IS_SWITCH)
+ add_nro_target(chiaki
+ chiaki-borealis
+ "Chiaki"
+ "H0neyBadger and thestr4ng3r"
+ "${CHIAKI_VERSION}"
+ "${CMAKE_CURRENT_SOURCE_DIR}/nro_icon.jpg"
+ "${CMAKE_CURRENT_SOURCE_DIR}/res")
+endif()
diff --git a/switch/README.md b/switch/README.md
new file mode 100644
index 0000000..2307fa4
--- /dev/null
+++ b/switch/README.md
@@ -0,0 +1,48 @@
+Nintendo Switch build instructions
+==================================
+this project requires the devkitpro toolchain.
+you can use your personal computer to install devkitpro
+but the easiest way is to use the following container.
+
+Build Project
+-------------
+```
+bash scripts/switch/run-podman-build-chiaki.sh
+```
+
+tools
+-----
+Push to homebrew Netloader
+```
+# where X.X.X.X is the IP of your switch
+bash scripts/switch/push-podman-build-chiaki.sh -a 192.168.0.200
+```
+
+Troubleshoot
+```
+# replace 0xCCB5C with the backtrace adress (PC - Backtrace Start Address)
+aarch64-none-elf-addr2line \
+ -e ./build_switch/switch/chiaki \
+ -f -p -C -a 0xCCB5C
+```
+
+Chiaki config file
+------------------
+The **chiaki.conf** is generated by the application.
+this file contains sensitive data. (do not share this file)
+```ini
+# required: PS*-*** (PS4/PS5 local name)
+# name from Settings > System > system information
+[PS*-***]
+# required: lan PlayStation IP address
+# IP from Settings > System > system information
+host_addr = *.*.*.*
+# required: sony oline id (login)
+psn_online_id = ps_online_id
+# required (PS4>7.0 Only): https://git.sr.ht/~thestr4ng3r/chiaki/tree/master/item/README.md#obtaining-your-psn-accountid
+psn_account_id = ps_base64_account_id
+# optional(default 60): remote play fps (must be 30 or 60)
+video_fps = 60
+# optional(default 720p): remote play resolution (must be 720p, 540p or 360p)
+video_resolution = 720p
+```
diff --git a/switch/borealis b/switch/borealis
new file mode 160000
index 0000000..eae1371
--- /dev/null
+++ b/switch/borealis
@@ -0,0 +1 @@
+Subproject commit eae1371831d6cebf11b8ebd4c611069bccc6fb9b
diff --git a/switch/include/discoverymanager.h b/switch/include/discoverymanager.h
new file mode 100644
index 0000000..49c8dcc
--- /dev/null
+++ b/switch/include/discoverymanager.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
+
+#ifndef CHIAKI_DISCOVERYMANAGER_H
+#define CHIAKI_DISCOVERYMANAGER_H
+
+#include