From f7c83e8416e61fc1e421f91f473191a5b47ae7c1 Mon Sep 17 00:00:00 2001 From: H0neyBadger Date: Sun, 25 Oct 2020 10:51:49 +0100 Subject: [PATCH] Add Nintendo Switch Borealis GUI (#349) --- .github/workflows/switch.yml | 12 +- .gitmodules | 3 + CMakeLists.txt | 30 +- README.md | 3 +- cmake/switch.cmake | 149 ++-- lib/CMakeLists.txt | 11 +- scripts/switch/build.sh | 22 +- scripts/switch/push-docker-build-chiaki.sh | 10 + scripts/switch/run-docker-build-chiaki.sh | 3 +- switch/CMakeLists.txt | 46 ++ switch/README.md | 64 ++ switch/include/discoverymanager.h | 61 ++ switch/include/exception.h | 35 + switch/include/gui.h | 102 +++ switch/include/host.h | 129 ++++ switch/include/io.h | 136 ++++ switch/include/settings.h | 105 +++ switch/res/add-24px.svg | 1 + switch/res/console.svg | 1 + switch/res/discover-24px.svg | 1 + switch/res/discover-off-24px.svg | 1 + switch/res/i18n/en-US | 1 + switch/res/icon.jpg | Bin 0 -> 25090 bytes switch/res/inter | 1 + switch/res/material | 1 + switch/res/settings-20px.svg | 1 + switch/src/discoverymanager.cpp | 259 +++++++ switch/src/gui.cpp | 514 +++++++++++++ switch/src/host.cpp | 287 ++++++++ switch/src/io.cpp | 797 +++++++++++++++++++++ switch/src/main.cpp | 175 +++++ switch/src/settings.cpp | 561 +++++++++++++++ third-party/CMakeLists.txt | 71 ++ third-party/borealis | 1 + 34 files changed, 3470 insertions(+), 124 deletions(-) create mode 100755 scripts/switch/push-docker-build-chiaki.sh create mode 100644 switch/CMakeLists.txt create mode 100644 switch/README.md create mode 100644 switch/include/discoverymanager.h create mode 100644 switch/include/exception.h create mode 100644 switch/include/gui.h create mode 100644 switch/include/host.h create mode 100644 switch/include/io.h create mode 100644 switch/include/settings.h create mode 120000 switch/res/add-24px.svg create mode 120000 switch/res/console.svg create mode 120000 switch/res/discover-24px.svg create mode 120000 switch/res/discover-off-24px.svg create mode 120000 switch/res/i18n/en-US create mode 100644 switch/res/icon.jpg create mode 120000 switch/res/inter create mode 120000 switch/res/material create mode 120000 switch/res/settings-20px.svg create mode 100644 switch/src/discoverymanager.cpp create mode 100644 switch/src/gui.cpp create mode 100644 switch/src/host.cpp create mode 100644 switch/src/io.cpp create mode 100644 switch/src/main.cpp create mode 100644 switch/src/settings.cpp create mode 160000 third-party/borealis diff --git a/.github/workflows/switch.yml b/.github/workflows/switch.yml index 7c5effe..b365ab6 100644 --- a/.github/workflows/switch.yml +++ b/.github/workflows/switch.yml @@ -1,5 +1,9 @@ name: Switch -on: [push] +on: + push: + branches: + - master + pull_request: jobs: build: @@ -13,4 +17,8 @@ jobs: git submodule update - name: Build Chiaki run: scripts/switch/run-docker-build-chiaki.sh - + - name: Upload chiaki.nro + uses: actions/upload-artifact@v1 + with: + name: chiaki.nro + path: ./build_switch/switch/chiaki.nro diff --git a/.gitmodules b/.gitmodules index 1d25e40..30d4edd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "android/app/src/main/cpp/oboe"] path = android/app/src/main/cpp/oboe url = https://github.com/google/oboe +[submodule "third-party/borealis"] + path = third-party/borealis + url = https://github.com/natinusala/borealis.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f75bb09..1642cbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,17 +40,28 @@ include(CPack) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_CURRENT_SOURCE_DIR}/setsu/cmake") # configure nintendo switch toolchain -if(CHIAKI_ENABLE_SWITCH AND CHIAKI_ENABLE_SWITCH_LINUX) - # CHIAKI_ENABLE_SWITCH_LINUX is a special testing version - # the aim is to troubleshoot nitendo switch chiaki verison - # from a x86 linux os - add_definitions(-DCHIAKI_ENABLE_SWITCH_LINUX) - set(CMAKE_BUILD_TYPE Debug) -elseif(CHIAKI_ENABLE_SWITCH) - add_definitions(-D__SWITCH__) +if(CHIAKI_ENABLE_SWITCH) + # load switch.cmake toolchain form ./cmake folder + # include(switch) + # to compile borealis third party + set(CMAKE_CXX_STANDARD 17) # TODO check if android ... or other versions are enabled # force mbedtls as crypto lib set(CHIAKI_LIB_ENABLE_MBEDTLS ON) + + if(CHIAKI_SWITCH_ENABLE_LINUX) + # CHIAKI_SWITCH_ENABLE_LINUX is a special testing version + # the aim is to troubleshoot nitendo switch chiaki verison + # from a x86 linux os + add_definitions(-DCHIAKI_SWITCH_ENABLE_LINUX) + add_definitions(-DBOREALIS_RESOURCES="./switch/res/") + set(CMAKE_BUILD_TYPE Debug) + add_definitions(-DDEBUG_OPENGL) + else() + add_definitions(-D__SWITCH__) + # use symbolic links to load font .. + add_definitions(-DBOREALIS_RESOURCES="romfs:/") + endif() endif() if(CHIAKI_USE_SYSTEM_JERASURE) @@ -120,6 +131,5 @@ if(CHIAKI_ENABLE_ANDROID) endif() if(CHIAKI_ENABLE_SWITCH) - #TODO - #add_subdirectory(switch) + add_subdirectory(switch) endif() diff --git a/README.md b/README.md index 0831ecf..e710b7c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Travis Build Status](https://travis-ci.com/thestr4ng3r/chiaki.svg?branch=master)](https://travis-ci.com/thestr4ng3r/chiaki) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/dnj9cmx5mmaaawer?svg=true)](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [![builds.sr.ht Status](https://builds.sr.ht/~thestr4ng3r/chiaki.svg)](https://builds.sr.ht/~thestr4ng3r/chiaki?) Chiaki is a Free and Open Source Software Client for PlayStation 4 Remote Play -for Linux, FreeBSD, OpenBSD, Android, macOS, Windows and potentially even more platforms. +for Linux, FreeBSD, OpenBSD, Android, macOS, Windows, Nintendo Switch and potentially even more platforms. ![Screenshot](assets/screenshot.png) @@ -37,6 +37,7 @@ You can download them [here](https://github.com/thestr4ng3r/chiaki/releases). * **Android**: Install from [Google Play](https://play.google.com/store/apps/details?id=com.metallic.chiaki), [F-Droid](https://f-droid.org/packages/com.metallic.chiaki/) or download the APK from GitHub. * **macOS**: Drag the application from the `.dmg` into your Applications folder. * **Windows**: Extract the `.zip` file and execute `chiaki.exe`. +* **Switch**: Follow README specific [instructions](./switch/README.md) ### Building from Source diff --git a/cmake/switch.cmake b/cmake/switch.cmake index e9a07e5..05c18b9 100644 --- a/cmake/switch.cmake +++ b/cmake/switch.cmake @@ -1,116 +1,79 @@ -# https://github.com/nxengine/nxengine-evo - # Find DEVKITPRO -if(NOT DEFINED ENV{DEVKITPRO}) - message(FATAL_ERROR "You must have defined DEVKITPRO before calling cmake.") +if(NOT DEFINED ENV{DEVKITPRO} OR NOT DEFINED ENV{PORTLIBS_PREFIX}) + message(FATAL_ERROR "Please set DEVKITPRO & PORTLIBS_PREFIX env before calling cmake. https://devkitpro.org/wiki/Getting_Started") endif() -set(DEVKITPRO $ENV{DEVKITPRO}) +set(DEVKITPRO "$ENV{DEVKITPRO}") +set(PORTLIBS_PREFIX "$ENV{PORTLIBS_PREFIX}") -function(switchvar cmakevar var default) - # read or set env var - if(NOT DEFINED "ENV{$var}") - set("ENV{$var}" default) - endif() - set("$cmakevar" "ENV{$var}") -endfunction() +# include devkitpro toolchain +include("${DEVKITPRO}/switch.cmake") -# allow gcc -g to use -# aarch64-none-elf-addr2line -e build_switch/switch/chiaki -f -p -C -a 0xCCB5C -set(CMAKE_BUILD_TYPE Debug) +# Enable gcc -g, to use +# /opt/devkitpro/devkitA64/bin/aarch64-none-elf-addr2line -e build_switch/switch/chiaki -f -p -C -a 0xCCB5C +# set(CMAKE_BUILD_TYPE Debug) +# set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available" ) -set( TOOL_OS_SUFFIX "" ) -if( CMAKE_HOST_WIN32 ) - set( TOOL_OS_SUFFIX ".exe" ) -endif() +# FIXME rework this file to use the toolchain only +# https://github.com/diasurgical/devilutionX/pull/764 +set(ARCH "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec") +# set(CMAKE_C_FLAGS "-O2 -ffunction-sections ${ARCH}") +set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}") +# workaroud force -fPIE to avoid +# aarch64-none-elf/bin/ld: read-only segment has dynamic relocations +set(CMAKE_EXE_LINKER_FLAGS "-specs=${DEVKITPRO}/libnx/switch.specs ${ARCH} -fPIE -Wl,-Map,Output.map") -set(CMAKE_SYSTEM_PROCESSOR "armv8-a") -set(CMAKE_C_COMPILER "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") -set(CMAKE_CXX_COMPILER "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") -set(CMAKE_ASM_COMPILER "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-as${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") -set(CMAKE_STRIP "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip") -set(CMAKE_AR "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive") -set(CMAKE_LINKER "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker") -set(CMAKE_NM "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm") -set(CMAKE_OBJCOPY "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy") -set(CMAKE_OBJDUMP "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump") -set(CMAKE_RANLIB "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib") +# add portlibs to the list of include dir +include_directories("${PORTLIBS_PREFIX}/include") -# custom /opt/devkitpro/switchvars.sh -switchvar(PORTLIBS_PREFIX PORTLIBS_PREFIX "${DEVKITPRO}/portlibs/switch") -switchvar(ARCH ARCH "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec") -switchvar(CMAKE_C_FLAGS CFLAGS "${ARCH} -O2 -ffunction-sections -fdata-sections") -switchvar(CMAKE_CXX_FLAGS CXXFLAGS "${CMAKE_C_FLAGS}") -switchvar(CMAKE_CPP_FLAGS CPPFLAGS "-D__SWITCH__ -I${PORTLIBS_PREFIX}/include -isystem${DEVKITPRO}/libnx/include") -switchvar(CMAKE_LD_FLAGS LDFLAGS "${ARCH} -L${PORTLIBS_PREFIX}/lib -L${DEVKITPRO}/libnx/lib") -switchvar(LIBS LIBS "-lnx") - - - -# cache flags -set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) -set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) -set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) -set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) -set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) -set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) -set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" ) -set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" ) -set( CMAKE_EXE_LINKER_FLAGS "-mtp=soft -fPIE -L${DEVKITPRO}/portlibs/switch/lib -L${DEVKITPRO}/libnx/lib -specs=${DEVKITPRO}/libnx/switch.specs -g" CACHE STRING "executable linker flags" ) - -# we require the relocation table -set(CMAKE_C_FLAGS "-I/opt/devkitpro/libnx/include -D__SWITCH__ -march=armv8-a -mtune=cortex-a57 -mtp=soft -ffunction-sections -fdata-sections -fPIE") -set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fno-rtti") - -# switchvar(CMAKE_CXX_FLAGS CXXFLAGS "${CMAKE_C_FLAGS} -fno-rtti") -include_directories(${DEVKITPRO}/libnx/include ${PORTLIBS_PREFIX}/include) - -# where is the target environment -set(CMAKE_FIND_ROOT_PATH - ${DEVKITPRO}/devkitA64 - ${DEVKITPRO}/libnx - ${DEVKITPRO}/portlibs/switch) - -# only search for libraries and includes in toolchain -set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +# 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 () +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 () + 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}) + 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 - ) + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target} + COMMAND ${__NACP_COMMAND} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + VERBATIM + ) endfunction() function(add_nro_target target title author version icon romfs) - get_filename_component(target_we ${target} NAME_WE) - if (NOT ${target_we}.nacp) - __add_nacp(${target_we}.nacp ${title} ${author} ${version}) - endif () - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nro - COMMAND ${ELF2NRO} $ ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nro --icon=${icon} --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nacp - # --romfsdir=${romfs} - DEPENDS ${target} ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nacp - VERBATIM - ) - add_custom_target(${target_we}_nro ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nro) + get_filename_component(target_we ${target} NAME_WE) + if(NOT ${target_we}.nacp) + __add_nacp(${target_we}.nacp ${title} ${author} ${version}) + endif() + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nro + COMMAND ${ELF2NRO} $ + ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nro + --icon=${icon} + --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nacp + --romfsdir=${romfs} + DEPENDS ${target} ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nacp + VERBATIM + ) + add_custom_target(${target_we}_nro ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target_we}.nro) endfunction() - diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index facead5..f7083eb 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -101,8 +101,10 @@ target_link_libraries(chiaki-lib Threads::Threads) if(CHIAKI_LIB_ENABLE_MBEDTLS) # provided by mbedtls-static (mbedtls-devel) - # find_package(mbedcrypto REQUIRED) - target_link_libraries(chiaki-lib mbedtls mbedx509 mbedcrypto) + find_library(MBEDTLS mbedtls) + find_library(MBEDX509 mbedx509) + find_library(MBEDCRYPTO mbedcrypto) + target_link_libraries(chiaki-lib ${MBEDTLS} ${MBEDX509} ${MBEDCRYPTO}) elseif(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT) target_link_libraries(chiaki-lib OpenSSL_Crypto) else() @@ -111,11 +113,6 @@ else() target_link_libraries(chiaki-lib OpenSSL::Crypto) endif() -if(CHIAKI_ENABLE_SWITCH AND NOT CHIAKI_ENABLE_SWITCH_LINUX) - # to provides csrngGetRandomBytes - target_link_libraries(chiaki-lib nx) -endif() - target_link_libraries(chiaki-lib protobuf-nanopb-static) target_link_libraries(chiaki-lib Jerasure::Jerasure) diff --git a/scripts/switch/build.sh b/scripts/switch/build.sh index bd351f5..bbcb729 100755 --- a/scripts/switch/build.sh +++ b/scripts/switch/build.sh @@ -3,15 +3,14 @@ set -xveo pipefail arg1=$1 -CHIAKI_ENABLE_SWITCH_LINUX="ON" +CHIAKI_SWITCH_ENABLE_LINUX="ON" build="./build" if [ "$arg1" != "linux" ]; then - CHIAKI_ENABLE_SWITCH_LINUX="OFF" - source /opt/devkitpro/switchvars.sh - toolchain=../cmake/switch.cmake # TODO: devkitpro ships a toolchain in /opt/devkitpro/switch.cmake, but it's broken. - - export CC=${TOOL_PREFIX}gcc - export CXX=${TOOL_PREFIX}g++ + CHIAKI_SWITCH_ENABLE_LINUX="OFF" + # source /opt/devkitpro/switchvars.sh + # toolchain="${DEVKITPRO}/switch.cmake" + toolchain="cmake/switch.cmake" + export PORTLIBS_PREFIX="$(${DEVKITPRO}/portlibs_prefix.sh switch)" build="./build_switch" fi @@ -22,14 +21,17 @@ build_chiaki (){ pushd "${BASEDIR}" #rm -rf ./build - cmake -B "${build}" -DCMAKE_TOOLCHAIN_FILE=${toolchain} \ + cmake -B "${build}" \ + -DCMAKE_TOOLCHAIN_FILE=${toolchain} \ -DCHIAKI_ENABLE_TESTS=OFF \ -DCHIAKI_ENABLE_CLI=OFF \ -DCHIAKI_ENABLE_GUI=OFF \ -DCHIAKI_ENABLE_ANDROID=OFF \ -DCHIAKI_ENABLE_SWITCH=ON \ - -DCHIAKI_ENABLE_SWITCH_LINUX="${CHIAKI_ENABLE_SWITCH_LINUX}" \ - -DCHIAKI_LIB_ENABLE_MBEDTLS=ON + -DCHIAKI_SWITCH_ENABLE_LINUX="${CHIAKI_SWITCH_ENABLE_LINUX}" \ + -DCHIAKI_LIB_ENABLE_MBEDTLS=ON \ + # -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \ + # -DCMAKE_FIND_DEBUG_MODE=ON pushd "${BASEDIR}/${build}" make -j8 diff --git a/scripts/switch/push-docker-build-chiaki.sh b/scripts/switch/push-docker-build-chiaki.sh new file mode 100755 index 0000000..ff66e40 --- /dev/null +++ b/scripts/switch/push-docker-build-chiaki.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd "`dirname $(readlink -f ${0})`/../.." + +docker run \ + -v "`pwd`:/build/chiaki" \ + -p 28771:28771 -ti \ + chiaki-switch \ + -c "/opt/devkitpro/tools/bin/nxlink -a $@ -s /build/chiaki/build_switch/switch/chiaki.nro" + diff --git a/scripts/switch/run-docker-build-chiaki.sh b/scripts/switch/run-docker-build-chiaki.sh index 0fe7ad6..c81c788 100755 --- a/scripts/switch/run-docker-build-chiaki.sh +++ b/scripts/switch/run-docker-build-chiaki.sh @@ -4,7 +4,8 @@ cd "`dirname $(readlink -f ${0})`/../.." docker run \ -v "`pwd`:/build/chiaki" \ + -w "/build/chiaki" \ -t \ thestr4ng3r/chiaki-build-switch \ - -c "cd /build/chiaki && scripts/switch/build.sh" + -c "scripts/switch/build.sh" diff --git a/switch/CMakeLists.txt b/switch/CMakeLists.txt new file mode 100644 index 0000000..62fa146 --- /dev/null +++ b/switch/CMakeLists.txt @@ -0,0 +1,46 @@ + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +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 WIN32 + src/discoverymanager.cpp + src/settings.cpp + src/io.cpp + src/host.cpp + src/main.cpp + src/gui.cpp) + +target_include_directories(chiaki PRIVATE include) + +target_link_libraries(chiaki + chiaki-lib + borealis + ${SDL2} + FFMPEG::avcodec + FFMPEG::avutil + FFMPEG::swscale + ${SWRESAMPLE} + ${SWSCALE}) + +# OS links +if(CHIAKI_SWITCH_ENABLE_LINUX) + target_link_libraries(chiaki dl) +else() + # libnx is forced by the switch toolchain + find_library(Z z) + find_library(GLAPI glapi) + find_library(DRM_NOUVEAU drm_nouveau) + target_link_libraries(chiaki ${Z} ${GLAPI} ${DRM_NOUVEAU}) +endif() + +install(TARGETS chiaki + RUNTIME DESTINATION bin + BUNDLE DESTINATION bin) + +if(CHIAKI_ENABLE_SWITCH AND NOT CHIAKI_SWITCH_ENABLE_LINUX) + add_nro_target(chiaki "chiaki" "Chiaki team" "${CHIAKI_VERSION}" "${CMAKE_SOURCE_DIR}/switch/res/icon.jpg" "${CMAKE_SOURCE_DIR}/switch/res") +endif() diff --git a/switch/README.md b/switch/README.md new file mode 100644 index 0000000..e536d9b --- /dev/null +++ b/switch/README.md @@ -0,0 +1,64 @@ +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 container image +--------------------- +``` +bash scripts/switch/build-docker-image.sh +``` + +Run container +------------- +from the project's [root folder](../) +``` +docker run -it --rm \ + -v "$(pwd):/build" \ + -p 28771:28771 \ + chiaki-switch +``` + +Build Project +------------- +``` +bash scripts/switch/run-docker-build-chiaki.sh +``` + +tools +----- +Push to homebrew Netloader +``` +# where X.X.X.X is the IP of your switch +scripts/switch/push-docker-build-chiaki.sh 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: PS4-XXX (PS4 local name) +# name from PS4 Settings > System > system information +[PS4-XXX] +# required: lan PS4 IP address +# IP from PS4 Settings > System > system information +host_ip = X.X.X.X +# required: sony oline id (login) +psn_online_id = ps4_online_id +# required (PS4>7.0 Only): https://github.com/thestr4ng3r/chiaki#obtaining-your-psn-accountid +psn_account_id = ps4_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/include/discoverymanager.h b/switch/include/discoverymanager.h new file mode 100644 index 0000000..420eef1 --- /dev/null +++ b/switch/include/discoverymanager.h @@ -0,0 +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 . + */ + +#ifndef CHIAKI_DISCOVERYMANAGER_H +#define CHIAKI_DISCOVERYMANAGER_H + +#include +#include + +#include +#include + +#include "host.h" +#include "settings.h" + +static void Discovery(ChiakiDiscoveryHost*, void*); + +class DiscoveryManager +{ + private: + Settings * settings; + ChiakiLog * log; + ChiakiDiscoveryService service; + ChiakiDiscovery discovery; + struct sockaddr * host_addr; + size_t host_addr_len; + uint32_t GetIPv4BroadcastAddr(); + bool service_enable; + public: + typedef enum hoststate + { + UNKNOWN, + READY, + STANDBY, + SHUTDOWN, + } HostState; + + DiscoveryManager(Settings *settings); + ~DiscoveryManager(); + void SetService(bool); + int Send(); + int Send(struct sockaddr *host_addr, size_t host_addr_len); + int Send(const char *); + void DiscoveryCB(ChiakiDiscoveryHost*); +}; + +#endif //CHIAKI_DISCOVERYMANAGER_H diff --git a/switch/include/exception.h b/switch/include/exception.h new file mode 100644 index 0000000..a971b03 --- /dev/null +++ b/switch/include/exception.h @@ -0,0 +1,35 @@ +/* + * 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_EXCEPTION_H +#define CHIAKI_EXCEPTION_H + +#include + +#include + +class Exception : public std::exception +{ + private: + const char *msg; + + public: + explicit Exception(const char *msg) : msg(msg) {} + const char *what() const noexcept override { return msg; } +}; + +#endif // CHIAKI_EXCEPTION_H diff --git a/switch/include/gui.h b/switch/include/gui.h new file mode 100644 index 0000000..378fb2e --- /dev/null +++ b/switch/include/gui.h @@ -0,0 +1,102 @@ +/* + * 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_GUI_H +#define CHIAKI_GUI_H + +#include +#include +#include "nanovg.h" +#include "nanovg_gl.h" +#include "nanovg_gl_utils.h" + +#include + +#include +#include + +#include "host.h" +#include "settings.h" +#include "discoverymanager.h" +#include "io.h" + +class HostInterface +{ + private: + brls::TabFrame * root; + IO * io; + Host * host; + Settings * settings; + brls::List * hostList; + bool connected = false; + public: + HostInterface(brls::List * hostList, IO * io, Host * host, Settings * settings); + ~HostInterface(); + + void Register(bool pin_incorrect); + void Wakeup(brls::View * view); + void Connect(brls::View * view); + void ConnectSession(); + void Disconnect(); + bool Stream(); + bool CloseStream(ChiakiQuitEvent * quit); +}; + +class MainApplication +{ + private: + ChiakiLog * log; + std::map * hosts; + Settings * settings; + DiscoveryManager * discoverymanager; + IO * io; + brls::TabFrame * rootFrame; + std::map host_menuitems; + bool BuildConfigurationMenu(brls::List *, Host * host = nullptr); + public: + MainApplication(std::map * hosts, + Settings * settings, DiscoveryManager * discoverymanager, + IO * io, ChiakiLog * log); + + ~MainApplication(); + bool Load(); +}; + +class PS4RemotePlay : public brls::View +{ + private: + brls::AppletFrame * frame; + // to display stream on screen + IO * io; + // to send gamepad inputs + Host * host; + brls::Label * label; + ChiakiControllerState state = { 0 }; + // FPS calculation + // double base_time; + // int frame_counter = 0; + // int fps = 0; + + public: + PS4RemotePlay(IO * io, Host * host); + ~PS4RemotePlay(); + + void draw(NVGcontext * vg, int x, int y, unsigned width, unsigned height, brls::Style * style, brls::FrameContext * ctx) override; +}; + +#endif // CHIAKI_GUI_H + diff --git a/switch/include/host.h b/switch/include/host.h new file mode 100644 index 0000000..2a8c5e3 --- /dev/null +++ b/switch/include/host.h @@ -0,0 +1,129 @@ +/* + * 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_HOST_H +#define CHIAKI_HOST_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "exception.h" +#include "io.h" +#include "settings.h" + +class DiscoveryManager; +static void Discovery(ChiakiDiscoveryHost*, void *); +static void InitAudioCB(unsigned int channels, unsigned int rate, void * user); +static bool VideoCB(uint8_t * buf, size_t buf_size, void * user); +static void AudioCB(int16_t * buf, size_t samples_count, void * user); +static void RegistEventCB(ChiakiRegistEvent * event, void * user); +static void EventCB(ChiakiEvent * event, void * user); + +enum RegistError +{ + HOST_REGISTER_OK, + HOST_REGISTER_ERROR_SETTING_PSNACCOUNTID, + HOST_REGISTER_ERROR_SETTING_PSNONLINEID +}; + +class Settings; + +class Host +{ + private: + ChiakiLog * log = nullptr; + Settings * settings = nullptr; + //video config + ChiakiVideoResolutionPreset video_resolution = CHIAKI_VIDEO_RESOLUTION_PRESET_720p; + ChiakiVideoFPSPreset video_fps = CHIAKI_VIDEO_FPS_PRESET_60; + // info from discovery manager + int device_discovery_protocol_version = 0; + std::string host_type; + // user info + std::string psn_online_id = ""; + std::string psn_account_id = ""; + // info from regist/settings + ChiakiRegist regist = {}; + ChiakiRegistInfo regist_info = {}; + std::function chiaki_regist_event_type_finished_canceled = nullptr; + std::function chiaki_regist_event_type_finished_failed = nullptr; + std::function chiaki_regist_event_type_finished_success = nullptr; + + std::string ap_ssid; + std::string ap_bssid; + std::string ap_key; + std::string ap_name; + std::string ps4_nickname; + // mac address = 48 bits + uint8_t ps4_mac[6] = {0}; + char rp_regist_key[CHIAKI_SESSION_AUTH_SIZE] = {0}; + uint32_t rp_key_type = 0; + uint8_t rp_key[0x10] = {0}; + // manage stream session + bool session_init = false; + ChiakiSession session; + ChiakiOpusDecoder opus_decoder; + ChiakiConnectVideoProfile video_profile; + ChiakiControllerState keyboard_state; + friend class DiscoveryManager; + friend class Settings; + public: + // internal state + ChiakiDiscoveryHostState state = CHIAKI_DISCOVERY_HOST_STATE_UNKNOWN; + bool discovered = false; + bool registered = false; + // rp_key_data is true when rp_key, rp_regist_key, rp_key_type + bool rp_key_data = false; + std::string host_name; + int system_version = 0; + // sony's host_id == mac addr without colon + std::string host_id; + std::string host_addr; + Host(ChiakiLog * log, Settings * settings, std::string host_name); + ~Host(); + bool GetVideoResolution(int * ret_width, int * ret_height); + int Register(std::string pin); + int Wakeup(); + int InitSession(IO *); + int FiniSession(); + void StopSession(); + void StartSession(); + void SendFeedbackState(ChiakiControllerState*); + void RegistCB(ChiakiRegistEvent *); + + void SetRegistEventTypeFinishedCanceled(std::function chiaki_regist_event_type_finished_canceled) + { + this->chiaki_regist_event_type_finished_canceled = chiaki_regist_event_type_finished_canceled; + }; + void SetRegistEventTypeFinishedFailed(std::function chiaki_regist_event_type_finished_failed) + { + this->chiaki_regist_event_type_finished_failed = chiaki_regist_event_type_finished_failed; + }; + void SetRegistEventTypeFinishedSuccess(std::function chiaki_regist_event_type_finished_success) + { + this->chiaki_regist_event_type_finished_success = chiaki_regist_event_type_finished_success; + }; +}; + +#endif diff --git a/switch/include/io.h b/switch/include/io.h new file mode 100644 index 0000000..69ac90f --- /dev/null +++ b/switch/include/io.h @@ -0,0 +1,136 @@ +/* + * 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_IO_H +#define CHIAKI_IO_H + +#include +#include +#include + +#include // glad library (OpenGL loader) + +#include + + +/* +https://github.com/devkitPro/switch-glad/blob/master/include/glad/glad.h +https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D4.3&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic + +Language/Generator: C/C++ +Specification: gl +APIs: gl=4.3 +Profile: core +Extensions: +GL_EXT_texture_compression_s3tc, +GL_EXT_texture_filter_anisotropic +Loader: False +Local files: False +Omit khrplatform: False +Reproducible: False +*/ + +#include +extern "C" +{ +#include +#include +#include +} + +#include +#include + +#include "exception.h" + +#define PLANES_COUNT 3 +#define SDL_JOYSTICK_COUNT 2 + +class IO +{ + private: + ChiakiLog * log; + int video_width; + int video_height; + bool quit = false; + // opengl reader writer + std::mutex mtx; + // default nintendo switch res + int screen_width = 1280; + int screen_height = 720; + std::function chiaki_event_connected_cb = nullptr; + std::function chiaki_even_login_pin_request_cb = nullptr; + std::function chiaki_event_quit_cb = nullptr; + AVCodec * codec; + AVCodecContext * codec_context; + AVFrame * frame; + SDL_AudioDeviceID sdl_audio_device_id = 0; + SDL_Event sdl_event; + SDL_Joystick * sdl_joystick_ptr[SDL_JOYSTICK_COUNT] = {0}; + GLuint vao; + GLuint vbo; + GLuint tex[PLANES_COUNT]; + GLuint pbo[PLANES_COUNT]; + GLuint vert; + GLuint frag; + GLuint prog; + private: + bool InitAVCodec(); + bool InitOpenGl(); + bool InitOpenGlTextures(); + bool InitOpenGlShader(); + void OpenGlDraw(); +#ifdef DEBUG_OPENGL + void CheckGLError(const char * func, const char * file, int line); + void DumpShaderError(GLuint prog, const char * func, const char * file, int line); + void DumpProgramError(GLuint prog, const char * func, const char * file, int line); +#endif + GLuint CreateAndCompileShader(GLenum type, const char * source); + void SetOpenGlYUVPixels(AVFrame * frame); + bool ReadGameKeys(SDL_Event * event, ChiakiControllerState * state); + bool ReadGameTouchScreen(ChiakiControllerState * state); + public: + IO(ChiakiLog * log); + ~IO(); + void SetMesaConfig(); + bool VideoCB(uint8_t * buf, size_t buf_size); + void SetEventConnectedCallback(std::function chiaki_event_connected_cb) + { + this->chiaki_event_connected_cb = chiaki_event_connected_cb; + }; + void SetEventLoginPinRequestCallback(std::function chiaki_even_login_pin_request_cb) + { + this->chiaki_even_login_pin_request_cb = chiaki_even_login_pin_request_cb; + }; + void SetEventQuitCallback(std::function chiaki_event_quit_cb) + { + this->chiaki_event_quit_cb = chiaki_event_quit_cb; + }; + void InitAudioCB(unsigned int channels, unsigned int rate); + void AudioCB(int16_t * buf, size_t samples_count); + void EventCB(ChiakiEvent *event); + bool InitVideo(int video_width, int video_height, int screen_width, int screen_height); + bool FreeVideo(); + bool InitJoystick(); + bool FreeJoystick(); + bool ReadUserKeyboard(char * buffer, size_t buffer_size); + bool MainLoop(ChiakiControllerState * state); +}; + +#endif //CHIAKI_IO_H + + diff --git a/switch/include/settings.h b/switch/include/settings.h new file mode 100644 index 0000000..abcbe46 --- /dev/null +++ b/switch/include/settings.h @@ -0,0 +1,105 @@ +/* + * 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_SETTINGS_H +#define CHIAKI_SETTINGS_H + +#include + +#include "host.h" +#include + +// mutual host and settings +class Host; + +class Settings +{ + private: + ChiakiLog * log; + const char * filename = "chiaki.conf"; + + std::map *hosts; + // global_settings from psedo INI file + ChiakiVideoResolutionPreset global_video_resolution = CHIAKI_VIDEO_RESOLUTION_PRESET_720p; + ChiakiVideoFPSPreset global_video_fps = CHIAKI_VIDEO_FPS_PRESET_60; + std::string global_psn_online_id = ""; + std::string global_psn_account_id = ""; + + typedef enum configurationitem + { + UNKNOWN, + HOST_NAME, + HOST_IP, + PSN_ONLINE_ID, + PSN_ACCOUNT_ID, + RP_KEY, + RP_KEY_TYPE, + RP_REGIST_KEY, + VIDEO_RESOLUTION, + VIDEO_FPS, + } ConfigurationItem; + + // dummy parser implementation + // the aim is not to have bulletproof parser + // the goal is to read/write inernal flat configuration file + const std::map re_map = { + { HOST_NAME, std::regex("^\\[\\s*(.+)\\s*\\]") }, + { HOST_IP, std::regex("^\\s*host_ip\\s*=\\s*\"?(\\d+\\.\\d+\\.\\d+\\.\\d+)\"?") }, + { PSN_ONLINE_ID, std::regex("^\\s*psn_online_id\\s*=\\s*\"?(\\w+)\"?") }, + { PSN_ACCOUNT_ID, std::regex("^\\s*psn_account_id\\s*=\\s*\"?([\\w/=+]+)\"?") }, + { RP_KEY, std::regex("^\\s*rp_key\\s*=\\s*\"?([\\w/=+]+)\"?") }, + { RP_KEY_TYPE, std::regex("^\\s*rp_key_type\\s*=\\s*\"?(\\d)\"?") }, + { RP_REGIST_KEY, std::regex("^\\s*rp_regist_key\\s*=\\s*\"?([\\w/=+]+)\"?") }, + { VIDEO_RESOLUTION, std::regex("^\\s*video_resolution\\s*=\\s*\"?(1080p|720p|540p|360p)\"?") }, + { VIDEO_FPS, std::regex("^\\s*video_fps\\s*=\\s*\"?(60|30)\"?") }, + }; + + ConfigurationItem ParseLine(std::string * line, std::string * value); + size_t GetB64encodeSize(size_t); + public: + Settings(ChiakiLog * log, std::map * hosts): log(log), hosts(hosts){}; + Host * GetOrCreateHost(std::string * host_name); + ChiakiLog* GetLogger(); + std::string GetPSNOnlineID(Host * host); + std::string GetPSNAccountID(Host * host); + void SetPSNOnlineID(Host * host, std::string psn_online_id); + void SetPSNAccountID(Host * host, std::string psn_account_id); + ChiakiVideoResolutionPreset GetVideoResolution(Host * host); + ChiakiVideoFPSPreset GetVideoFPS(Host * host); + std::string ResolutionPresetToString(ChiakiVideoResolutionPreset resolution); + std::string FPSPresetToString(ChiakiVideoFPSPreset fps); + ChiakiVideoResolutionPreset StringToResolutionPreset(std::string value); + ChiakiVideoFPSPreset StringToFPSPreset(std::string value); + int ResolutionPresetToInt(ChiakiVideoResolutionPreset resolution); + int FPSPresetToInt(ChiakiVideoFPSPreset fps); + void SetVideoResolution(Host * host, ChiakiVideoResolutionPreset value); + void SetVideoFPS(Host * host, ChiakiVideoFPSPreset value); + void SetVideoResolution(Host * host, std::string value); + void SetVideoFPS(Host * host, std::string value); + std::string GetHostIPAddr(Host * host); + std::string GetHostName(Host * host); + bool SetHostRPKeyType(Host * host, std::string value); + int GetHostRPKeyType(Host * host); + std::string GetHostRPKey(Host * host); + std::string GetHostRPRegistKey(Host * host); + bool SetHostRPKey(Host * host, std::string rp_key_b64); + bool SetHostRPRegistKey(Host * host, std::string rp_regist_key_b64); + void ParseFile(); + int WriteFile(); +}; + +#endif // CHIAKI_SETTINGS_H diff --git a/switch/res/add-24px.svg b/switch/res/add-24px.svg new file mode 120000 index 0000000..4a76ba4 --- /dev/null +++ b/switch/res/add-24px.svg @@ -0,0 +1 @@ +../../gui/res/add-24px.svg \ No newline at end of file diff --git a/switch/res/console.svg b/switch/res/console.svg new file mode 120000 index 0000000..a7f858c --- /dev/null +++ b/switch/res/console.svg @@ -0,0 +1 @@ +../../assets/console.svg \ No newline at end of file diff --git a/switch/res/discover-24px.svg b/switch/res/discover-24px.svg new file mode 120000 index 0000000..a7c3cc3 --- /dev/null +++ b/switch/res/discover-24px.svg @@ -0,0 +1 @@ +../../gui/res/discover-24px.svg \ No newline at end of file diff --git a/switch/res/discover-off-24px.svg b/switch/res/discover-off-24px.svg new file mode 120000 index 0000000..2c80caa --- /dev/null +++ b/switch/res/discover-off-24px.svg @@ -0,0 +1 @@ +../../gui/res/discover-off-24px.svg \ No newline at end of file diff --git a/switch/res/i18n/en-US b/switch/res/i18n/en-US new file mode 120000 index 0000000..dec3de9 --- /dev/null +++ b/switch/res/i18n/en-US @@ -0,0 +1 @@ +../../../third-party/borealis/resources/i18n/en-US \ No newline at end of file diff --git a/switch/res/icon.jpg b/switch/res/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb515da077efce82a1d9aab5241a7cf15d30fd57 GIT binary patch literal 25090 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. + */ + +#ifdef __SWITCH__ +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#define PING_MS 500 +#define HOSTS_MAX 16 +#define DROP_PINGS 3 + +static void Discovery(ChiakiDiscoveryHost * discovered_hosts, size_t hosts_count, void * user) +{ + DiscoveryManager * dm = (DiscoveryManager *) user; + for(size_t i=0; i < hosts_count; i++) + { + dm->DiscoveryCB(discovered_hosts+i); + } +} + + +DiscoveryManager::DiscoveryManager(Settings *settings) + : settings(settings), host_addr(nullptr), host_addr_len(0) +{ + this->log = this->settings->GetLogger(); +} + +DiscoveryManager::~DiscoveryManager() +{ + // join discovery thread + if(this->service_enable) + { + SetService(false); + } + + chiaki_discovery_fini(&this->discovery); +} + +void DiscoveryManager::SetService(bool enable) +{ + if(this->service_enable == enable) + { + return; + } + + this->service_enable = enable; + + if(enable) + { + ChiakiDiscoveryServiceOptions options; + options.ping_ms = PING_MS; + options.hosts_max = HOSTS_MAX; + options.host_drop_pings = DROP_PINGS; + options.cb = Discovery; + options.cb_user = this; + + sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_port = htons(CHIAKI_DISCOVERY_PORT); + addr.sin_addr.s_addr = GetIPv4BroadcastAddr(); + options.send_addr = reinterpret_cast(&addr); + options.send_addr_size = sizeof(addr); + + ChiakiErrorCode err = chiaki_discovery_service_init(&this->service, &options, log); + if(err != CHIAKI_ERR_SUCCESS) + { + this->service_enable = false; + CHIAKI_LOGE(this->log, "DiscoveryManager failed to init Discovery Service"); + return; + } + } + else + { + chiaki_discovery_service_fini(&this->service); + } +} + +uint32_t DiscoveryManager::GetIPv4BroadcastAddr() +{ +#ifdef __SWITCH__ + uint32_t current_addr, subnet_mask; + // init nintendo net interface service + Result rc = nifmInitialize(NifmServiceType_User); + if (R_SUCCEEDED(rc)) + { + // read current IP and netmask + rc = nifmGetCurrentIpConfigInfo( + ¤t_addr, &subnet_mask, + NULL, NULL, NULL); + nifmExit(); + } + else + { + CHIAKI_LOGE(this->log, "Failed to get nintendo nifmGetCurrentIpConfigInfo"); + return 1; + } + return current_addr | (~subnet_mask); +#else + return 0xffffffff; +#endif +} + +int DiscoveryManager::Send(struct sockaddr *host_addr, size_t host_addr_len) +{ + if(!host_addr) + { + CHIAKI_LOGE(log, "Null sockaddr"); + return 1; + } + ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT); + + ChiakiDiscoveryPacket packet; + memset(&packet, 0, sizeof(packet)); + packet.cmd = CHIAKI_DISCOVERY_CMD_SRCH; + + chiaki_discovery_send(&this->discovery, &packet, this->host_addr, this->host_addr_len); + return 0; +} + +int DiscoveryManager::Send(const char * discover_ip_dest) +{ + struct addrinfo * host_addrinfos; + int r = getaddrinfo(discover_ip_dest, NULL, NULL, &host_addrinfos); + if(r != 0) + { + CHIAKI_LOGE(log, "getaddrinfo failed"); + return 1; + } + + struct sockaddr * host_addr = nullptr; + socklen_t host_addr_len = 0; + + for(struct addrinfo *ai=host_addrinfos; ai; ai=ai->ai_next) + { + if(ai->ai_protocol != IPPROTO_UDP) + continue; + if(ai->ai_family != AF_INET) // TODO: IPv6 + continue; + + this->host_addr_len = ai->ai_addrlen; + this->host_addr = (struct sockaddr *)malloc(host_addr_len); + if(!this->host_addr) + break; + memcpy(this->host_addr, ai->ai_addr, this->host_addr_len); + } + + freeaddrinfo(host_addrinfos); + + if(!this->host_addr) + { + CHIAKI_LOGE(log, "Failed to get addr for hostname"); + return 1; + } + return DiscoveryManager::Send(this->host_addr, this->host_addr_len); +} + +int DiscoveryManager::Send() +{ + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = GetIPv4BroadcastAddr(); + addr.sin_port = htons(CHIAKI_DISCOVERY_PORT); + + this->host_addr_len = sizeof(sockaddr_in); + this->host_addr = (struct sockaddr *)malloc(host_addr_len); + memcpy(this->host_addr, &addr, this->host_addr_len); + + return DiscoveryManager::Send(this->host_addr, this->host_addr_len); +} + + +void DiscoveryManager::DiscoveryCB(ChiakiDiscoveryHost * discovered_host) +{ + // the user ptr is passed as + // chiaki_discovery_thread_start arg + + std::string key = discovered_host->host_name; + Host *host = this->settings->GetOrCreateHost(&key); + + CHIAKI_LOGI(this->log, "--"); + CHIAKI_LOGI(this->log, "Discovered Host:"); + CHIAKI_LOGI(this->log, "State: %s", chiaki_discovery_host_state_string(discovered_host->state)); + /* + host attr + uint32_t host_addr; + int system_version; + int device_discovery_protocol_version; + std::string host_name; + std::string host_type; + std::string host_id; + */ + host->state = discovered_host->state; + + // add host ptr to list + if(discovered_host->system_version) + { + // example: 07020001 + host->system_version = atoi(discovered_host->system_version); + CHIAKI_LOGI(this->log, "System Version: %s", discovered_host->system_version); + } + + if(discovered_host->device_discovery_protocol_version) + { + host->device_discovery_protocol_version = atoi(discovered_host->device_discovery_protocol_version); + CHIAKI_LOGI(this->log, "Device Discovery Protocol Version: %s", discovered_host->device_discovery_protocol_version); + } + + if(discovered_host->host_request_port) + CHIAKI_LOGI(this->log, "Request Port: %hu", (unsigned short)discovered_host->host_request_port); + + if(discovered_host->host_addr) + { + host->host_addr = discovered_host->host_addr; + CHIAKI_LOGI(this->log, "Host Addr: %s", discovered_host->host_addr); + } + + if(discovered_host->host_name) + { + host->host_name = discovered_host->host_name; + CHIAKI_LOGI(this->log, "Host Name: %s", discovered_host->host_name); + } + + if(discovered_host->host_type) + CHIAKI_LOGI(this->log, "Host Type: %s", discovered_host->host_type); + + if(discovered_host->host_id) + CHIAKI_LOGI(this->log, "Host ID: %s", discovered_host->host_id); + + if(discovered_host->running_app_titleid) + CHIAKI_LOGI(this->log, "Running App Title ID: %s", discovered_host->running_app_titleid); + + if(discovered_host->running_app_name) + CHIAKI_LOGI(this->log, "Running App Name: %s", discovered_host->running_app_name); + + CHIAKI_LOGI(this->log, "--"); +} + diff --git a/switch/src/gui.cpp b/switch/src/gui.cpp new file mode 100644 index 0000000..b3997b5 --- /dev/null +++ b/switch/src/gui.cpp @@ -0,0 +1,514 @@ +/* + * 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 "gui.h" + +#define SCREEN_W 1280 +#define SCREEN_H 720 + +// TODO +using namespace brls::i18n::literals; // for _i18n + +#define DIALOG(dialog, r) \ + brls::Dialog* d_##dialog = new brls::Dialog(r); \ + brls::GenericEvent::Callback cb_##dialog = [d_##dialog](brls::View* view) \ + { \ + d_##dialog->close(); \ + }; \ + d_##dialog->addButton("Ok", cb_##dialog); \ + d_##dialog->setCancelable(false); \ + d_##dialog->open(); \ + brls::Logger::info("Dialog: {0}", r); + + +HostInterface::HostInterface(brls::List * hostList, IO * io, Host * host, Settings * settings) + : hostList(hostList), io(io), host(host), settings(settings) +{ + brls::ListItem* connect = new brls::ListItem("Connect"); + connect->getClickEvent()->subscribe(std::bind(&HostInterface::Connect, this, std::placeholders::_1)); + this->hostList->addView(connect); + + brls::ListItem* wakeup = new brls::ListItem("Wakeup"); + wakeup->getClickEvent()->subscribe(std::bind(&HostInterface::Wakeup, this, std::placeholders::_1)); + this->hostList->addView(wakeup); + + // message delimiter + brls::Label* info = new brls::Label(brls::LabelStyle::REGULAR, + "Host configuration", true); + this->hostList->addView(info); + + // push opengl chiaki stream + // when the host is connected + this->io->SetEventConnectedCallback(std::bind(&HostInterface::Stream, this)); + this->io->SetEventQuitCallback(std::bind(&HostInterface::CloseStream, this, std::placeholders::_1)); +} + +HostInterface::~HostInterface() +{ + Disconnect(); +} + +void HostInterface::Register(bool pin_incorrect) +{ + if(pin_incorrect) + { + DIALOG(srfpvyps, "Session Registration Failed, Please verify your PS4 settings"); + return; + } + + // the host is not registered yet + brls::Dialog* peprpc = new brls::Dialog("Please enter your PS4 registration PIN code"); + brls::GenericEvent::Callback cb_peprpc = [this, peprpc](brls::View* view) + { + bool pin_provided = false; + char pin_input[9] = {0}; + std::string error_message; + + // use callback to ensure that the message is showed on screen + // before the the ReadUserKeyboard + peprpc->close(); + + pin_provided = this->io->ReadUserKeyboard(pin_input, sizeof(pin_input)); + if(pin_provided) + { + // prevent users form messing with the gui + brls::Application::blockInputs(); + int ret = this->host->Register(pin_input); + if(ret != HOST_REGISTER_OK) + { + switch(ret) + { + // account not configured + case HOST_REGISTER_ERROR_SETTING_PSNACCOUNTID: + brls::Application::notify("No PSN Account ID provided"); + brls::Application::unblockInputs(); + break; + case HOST_REGISTER_ERROR_SETTING_PSNONLINEID: + brls::Application::notify("No PSN Online ID provided"); + brls::Application::unblockInputs(); + break; + } + } + } + }; + peprpc->addButton("Ok", cb_peprpc); + peprpc->setCancelable(false); + peprpc->open(); +} + +void HostInterface::Wakeup(brls::View * view) +{ + if(!this->host->rp_key_data) + { + // the host is not registered yet + DIALOG(prypf, "Please register your PS4 first"); + } + else + { + int r = host->Wakeup(); + if(r == 0) + { + brls::Application::notify("PS4 Wakeup packet sent"); + } + else + { + brls::Application::notify("PS4 Wakeup packet failed"); + } + } +} + +void HostInterface::Connect(brls::View * view) +{ + // check that all requirements are met + if(this->host->state != CHIAKI_DISCOVERY_HOST_STATE_READY) + { + // host in standby mode + DIALOG(ptoyp, "Please turn on your PS4"); + return; + } + + if(!this->host->rp_key_data) + { + // user must provide psn id for registration + std::string account_id = this->settings->GetPSNAccountID(this->host); + std::string online_id = this->settings->GetPSNOnlineID(this->host); + if(this->host->system_version >= 7000000 && account_id.length() <= 0) + { + // PS4 firmware > 7.0 + DIALOG(upaid, "Undefined PSN Account ID (Please configure a valid psn_account_id)"); + return; + } + else if( this->host->system_version < 7000000 && this->host->system_version > 0 && online_id.length() <= 0) + { + // use oline ID for ps4 < 7.0 + DIALOG(upoid, "Undefined PSN Online ID (Please configure a valid psn_online_id)"); + return; + } + + // add HostConnected function to regist_event_type_finished_success + auto event_type_finished_success_cb = [this]() + { + // save RP keys + this->settings->WriteFile(); + // FIXME: may raise a connection refused + // when the connection is triggered + // just after the register success + sleep(2); + ConnectSession(); + // decrement block input token number + brls::Application::unblockInputs(); + }; + this->host->SetRegistEventTypeFinishedSuccess(event_type_finished_success_cb); + + auto event_type_finished_failed_cb = [this]() + { + // unlock user inputs + brls::Application::unblockInputs(); + brls::Application::notify("Registration failed"); + }; + this->host->SetRegistEventTypeFinishedFailed(event_type_finished_failed_cb); + + this->Register(false); + } + else + { + // the host is already registered + // start session directly + ConnectSession(); + } +} + +void HostInterface::ConnectSession() +{ + // ignore all user inputs (avoid double connect) + // user inputs are restored with the CloseStream + brls::Application::blockInputs(); + + // connect host sesssion + this->host->InitSession(this->io); + this->host->StartSession(); +} + +void HostInterface::Disconnect() +{ + if(this->connected) + { + brls::Application::popView(); + this->host->StopSession(); + this->connected = false; + } + this->host->FiniSession(); +} + +bool HostInterface::Stream() +{ + this->connected = true; + // https://github.com/natinusala/borealis/issues/59 + // disable 60 fps limit + brls::Application::setMaximumFPS(0); + + // show FPS counter + // brls::Application::setDisplayFramerate(true); + + // push raw opengl stream over borealis + brls::Application::pushView(new PS4RemotePlay(this->io, this->host)); + return true; +} + +bool HostInterface::CloseStream(ChiakiQuitEvent * quit) +{ + // session QUIT call back + brls::Application::unblockInputs(); + + // restore 60 fps limit + brls::Application::setMaximumFPS(60); + + // brls::Application::setDisplayFramerate(false); + /* + DIALOG(sqrs, chiaki_quit_reason_string(quit->reason)); + */ + brls::Application::notify(chiaki_quit_reason_string(quit->reason)); + Disconnect(); + return false; +} + +MainApplication::MainApplication(std::map * hosts, + Settings * settings, DiscoveryManager * discoverymanager, + IO * io, ChiakiLog * log) + : hosts(hosts), settings(settings), discoverymanager(discoverymanager), + io(io), log(log) +{ +} + +MainApplication::~MainApplication() +{ + this->discoverymanager->SetService(false); + //this->io->FreeJoystick(); + this->io->FreeVideo(); +} + +bool MainApplication::Load() +{ + this->discoverymanager->SetService(true); + // Init the app + brls::Logger::setLogLevel(brls::LogLevel::DEBUG); + + brls::i18n::loadTranslations(); + if (!brls::Application::init("Chiaki Remote play")) + { + brls::Logger::error("Unable to init Borealis application"); + return false; + } + + // init chiaki gl after borealis + // let borealis manage the main screen/window + + if(!io->InitVideo(0, 0, SCREEN_W, SCREEN_H)) + { + brls::Logger::error("Failed to initiate Video"); + } + + brls::Logger::info("Load sdl joysticks"); + if(!io->InitJoystick()) + { + brls::Logger::error("Faled to initiate Joysticks"); + } + + // Create a view + this->rootFrame = new brls::TabFrame(); + this->rootFrame->setTitle("Chiaki: Open Source PS4 Remote Play Client"); + this->rootFrame->setIcon(BOREALIS_ASSET("icon.jpg")); + + brls::List* config = new brls::List(); + BuildConfigurationMenu(config); + + this->rootFrame->addTab("Configuration", config); + // ---------------- + this->rootFrame->addSeparator(); + + // Add the root view to the stack + brls::Application::pushView(this->rootFrame); + while(brls::Application::mainLoop()) + { + for(auto it = this->hosts->begin(); it != this->hosts->end(); it++) + { + if(this->host_menuitems.find(&it->second) == this->host_menuitems.end()) + { + brls::List* new_host = new brls::List(); + this->host_menuitems[&it->second] = new_host; + // create host if udefined + HostInterface host_menu = HostInterface(new_host, this->io, &it->second, this->settings); + BuildConfigurationMenu(new_host, &it->second); + this->rootFrame->addTab(it->second.host_name.c_str(), new_host); + } + } + } + return true; +} + +bool MainApplication::BuildConfigurationMenu(brls::List * ls, Host * host) +{ + std::string psn_online_id_string = this->settings->GetPSNOnlineID(host); + brls::ListItem* psn_online_id = new brls::ListItem("PSN Online ID"); + psn_online_id->setValue(psn_online_id_string.c_str()); + auto psn_online_id_cb = [this, host, psn_online_id](brls::View * view) + { + char online_id[256] = {0}; + bool input = this->io->ReadUserKeyboard(online_id, sizeof(online_id)); + if(input) + { + // update gui + psn_online_id->setValue(online_id); + // push in setting + this->settings->SetPSNOnlineID(host, online_id); + // write on disk + this->settings->WriteFile(); + } + }; + psn_online_id->getClickEvent()->subscribe(psn_online_id_cb); + ls->addView(psn_online_id); + + std::string psn_account_id_string = this->settings->GetPSNAccountID(host); + brls::ListItem* psn_account_id = new brls::ListItem("PSN Account ID", "v7.0 and greater"); + psn_account_id->setValue(psn_account_id_string.c_str()); + auto psn_account_id_cb = [this, host, psn_account_id](brls::View * view) + { + char account_id[CHIAKI_PSN_ACCOUNT_ID_SIZE * 2] = {0}; + bool input = this->io->ReadUserKeyboard(account_id, sizeof(account_id)); + if(input) + { + // update gui + psn_account_id->setValue(account_id); + // push in setting + this->settings->SetPSNAccountID(host, account_id); + // write on disk + this->settings->WriteFile(); + } + }; + psn_account_id->getClickEvent()->subscribe(psn_account_id_cb); + ls->addView(psn_account_id); + + int value; + ChiakiVideoResolutionPreset resolution_preset = this->settings->GetVideoResolution(host); + switch(resolution_preset) + { + case CHIAKI_VIDEO_RESOLUTION_PRESET_720p: + value = 0; + break; + case CHIAKI_VIDEO_RESOLUTION_PRESET_540p: + value = 1; + break; + case CHIAKI_VIDEO_RESOLUTION_PRESET_360p: + value = 2; + break; + } + + brls::SelectListItem* resolution = new brls::SelectListItem( + "Resolution", { "720p", "540p", "360p" }, value); + + auto resolution_cb = [this, host](int result) + { + ChiakiVideoResolutionPreset value = CHIAKI_VIDEO_RESOLUTION_PRESET_720p; + switch(result) + { + case 0: + value = CHIAKI_VIDEO_RESOLUTION_PRESET_720p; + break; + case 1: + value = CHIAKI_VIDEO_RESOLUTION_PRESET_540p; + break; + case 2: + value = CHIAKI_VIDEO_RESOLUTION_PRESET_360p; + break; + } + this->settings->SetVideoResolution(host, value); + this->settings->WriteFile(); + }; + resolution->getValueSelectedEvent()->subscribe(resolution_cb); + ls->addView(resolution); + + ChiakiVideoFPSPreset fps_preset = this->settings->GetVideoFPS(host); + switch(fps_preset) + { + case CHIAKI_VIDEO_FPS_PRESET_60: + value = 0; + break; + case CHIAKI_VIDEO_FPS_PRESET_30: + value = 1; + break; + } + + brls::SelectListItem* fps = new brls::SelectListItem( + "FPS", { "60", "30"}, value); + + auto fps_cb = [this, host](int result) + { + ChiakiVideoFPSPreset value = CHIAKI_VIDEO_FPS_PRESET_60; + switch(result) + { + case 0: + value = CHIAKI_VIDEO_FPS_PRESET_60; + break; + case 1: + value = CHIAKI_VIDEO_FPS_PRESET_30; + break; + } + this->settings->SetVideoFPS(host, value); + this->settings->WriteFile(); + }; + + fps->getValueSelectedEvent()->subscribe(fps_cb); + ls->addView(fps); + + if(host != nullptr) + { + // message delimiter + brls::Label* info = new brls::Label(brls::LabelStyle::REGULAR, + "Host information", true); + ls->addView(info); + + std::string host_name_string = this->settings->GetHostName(host); + brls::ListItem* host_name = new brls::ListItem("PS4 Hostname"); + host_name->setValue(host_name_string.c_str()); + ls->addView(host_name); + + std::string host_ipaddr_string = settings->GetHostIPAddr(host); + brls::ListItem* host_ipaddr = new brls::ListItem("PS4 IP Address"); + host_ipaddr->setValue(host_ipaddr_string.c_str()); + ls->addView(host_ipaddr); + + std::string host_rp_regist_key_string = settings->GetHostRPRegistKey(host); + brls::ListItem* host_rp_regist_key = new brls::ListItem("RP Register Key"); + host_rp_regist_key->setValue(host_rp_regist_key_string.c_str()); + ls->addView(host_rp_regist_key); + + std::string host_rp_key_string = settings->GetHostRPKey(host); + brls::ListItem* host_rp_key = new brls::ListItem("RP Key"); + host_rp_key->setValue(host_rp_key_string.c_str()); + ls->addView(host_rp_key); + + std::string host_rp_key_type_string = std::to_string(settings->GetHostRPKeyType(host)); + brls::ListItem* host_rp_key_type = new brls::ListItem("RP Key type"); + host_rp_key_type->setValue(host_rp_key_type_string.c_str()); + ls->addView(host_rp_key_type); + + } + + return true; +} + +PS4RemotePlay::PS4RemotePlay(IO * io, Host * host) + : io(io), host(host) +{ + // store joycon/touchpad keys + for(int x=0; x < CHIAKI_CONTROLLER_TOUCHES_MAX; x++) + // start touchpad as "untouched" + this->state.touches[x].id = -1; + + // this->base_time=glfwGetTime(); +} + +void PS4RemotePlay::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) +{ + this->io->MainLoop(&this->state); + this->host->SendFeedbackState(&state); + + // FPS calculation + // this->frame_counter += 1; + // double frame_time = glfwGetTime(); + // if((frame_time - base_time) >= 1.0) + // { + // base_time += 1; + // //printf("FPS: %d\n", this->frame_counter); + // this->fps = this->frame_counter; + // this->frame_counter = 0; + // } + // nvgBeginPath(vg); + // nvgFillColor(vg, nvgRGBA(255,192,0,255)); + // nvgFontFaceId(vg, ctx->fontStash->regular); + // nvgFontSize(vg, style->Label.smallFontSize); + // nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + // char fps_str[9] = {0}; + // sprintf(fps_str, "FPS: %000d", this->fps); + // nvgText(vg, 5,10, fps_str, NULL); +} + +PS4RemotePlay::~PS4RemotePlay() +{ +} + diff --git a/switch/src/host.cpp b/switch/src/host.cpp new file mode 100644 index 0000000..2b3dcf3 --- /dev/null +++ b/switch/src/host.cpp @@ -0,0 +1,287 @@ +/* + * 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 "io.h" +#include "host.h" + + +static void InitAudioCB(unsigned int channels, unsigned int rate, void * user) +{ + IO * io = (IO *) user; + io->InitAudioCB(channels, rate); +} + +static bool VideoCB(uint8_t * buf, size_t buf_size, void * user) +{ + IO * io = (IO *) user; + return io->VideoCB(buf, buf_size); +} + +static void AudioCB(int16_t * buf, size_t samples_count, void * user) +{ + IO * io = (IO *) user; + io->AudioCB(buf, samples_count); +} + +static void EventCB(ChiakiEvent * event, void * user) +{ + IO * io = (IO *) user; + io->EventCB(event); +} + +static void RegistEventCB(ChiakiRegistEvent * event, void * user) +{ + Host * host = (Host *) user; + host->RegistCB(event); +} + +Host::Host(ChiakiLog * log, Settings * settings, std::string host_name) + : log(log), settings(settings), host_name(host_name) +{ +} + +Host::~Host() +{ +} + +int Host::Wakeup() +{ + if(strlen(this->rp_regist_key) > 8) + { + CHIAKI_LOGE(this->log, "Given registkey is too long"); + return 1; + } + else if (strlen(this->rp_regist_key) <=0) + { + CHIAKI_LOGE(this->log, "Given registkey is not defined"); + return 2; + } + + uint64_t credential = (uint64_t)strtoull(this->rp_regist_key, NULL, 16); + ChiakiErrorCode ret = chiaki_discovery_wakeup(this->log, NULL, host_addr.c_str(), credential); + if(ret == CHIAKI_ERR_SUCCESS) + { + //FIXME + } + return ret; +} + +int Host::Register(std::string pin) +{ + // use pin and accont_id to negociate secrets for session + // + // convert psn_account_id into uint8_t[CHIAKI_PSN_ACCOUNT_ID_SIZE] + // CHIAKI_PSN_ACCOUNT_ID_SIZE == 8 + std::string account_id = this->settings->GetPSNAccountID(this); + std::string online_id = this->settings->GetPSNOnlineID(this); + size_t account_id_size = sizeof(uint8_t[CHIAKI_PSN_ACCOUNT_ID_SIZE]); + + // PS4 firmware > 7.0 + if(this->system_version >= 7000000) + { + // use AccountID for ps4 > 7.0 + if(account_id.length() > 0) + { + chiaki_base64_decode(account_id.c_str(), account_id.length(), + regist_info.psn_account_id, &(account_id_size)); + regist_info.psn_online_id = nullptr; + } + else + { + CHIAKI_LOGE(this->log, "Undefined PSN Account ID (Please configure a valid psn_account_id)"); + return HOST_REGISTER_ERROR_SETTING_PSNACCOUNTID; + } + if(this->system_version >= 8000000) + regist_info.target = CHIAKI_TARGET_PS4_10; + else + regist_info.target = CHIAKI_TARGET_PS4_9; + } + else if( this->system_version < 7000000 && this->system_version > 0) + { + // use oline ID for ps4 < 7.0 + if(online_id.length() > 0) + { + regist_info.psn_online_id = this->psn_online_id.c_str(); + // regist_info.psn_account_id = {0}; + } + else + { + CHIAKI_LOGE(this->log, "Undefined PSN Online ID (Please configure a valid psn_online_id)"); + return HOST_REGISTER_ERROR_SETTING_PSNONLINEID; + } + regist_info.target = CHIAKI_TARGET_PS4_8; + } + else + { + CHIAKI_LOGE(this->log, "Undefined PS4 system version (please run discover first)"); + } + + this->regist_info.pin = atoi(pin.c_str()); + this->regist_info.host = this->host_addr.c_str(); + this->regist_info.broadcast = false; + CHIAKI_LOGI(this->log, "Registering to host `%s` `%s` with PSN AccountID `%s` pin `%s`", + this->host_name.c_str(), this->host_addr.c_str(), psn_account_id.c_str(), pin.c_str()); + chiaki_regist_start(&this->regist, this->log, &this->regist_info, RegistEventCB, this); + return HOST_REGISTER_OK; +} + +int Host::InitSession(IO * user) +{ + chiaki_connect_video_profile_preset(&(this->video_profile), + this->video_resolution, this->video_fps); + // Build chiaki ps4 stream session + chiaki_opus_decoder_init(&(this->opus_decoder), this->log); + ChiakiAudioSink audio_sink; + ChiakiConnectInfo chiaki_connect_info; + + chiaki_connect_info.host = this->host_addr.c_str(); + chiaki_connect_info.video_profile = this->video_profile; + memcpy(chiaki_connect_info.regist_key, this->rp_regist_key, sizeof(chiaki_connect_info.regist_key)); + memcpy(chiaki_connect_info.morning, this->rp_key, sizeof(chiaki_connect_info.morning)); + // set keybord state to 0 + memset(&(this->keyboard_state), 0, sizeof(keyboard_state)); + + ChiakiErrorCode err = chiaki_session_init(&(this->session), &chiaki_connect_info, this->log); + if(err != CHIAKI_ERR_SUCCESS) + throw Exception(chiaki_error_string(err)); + this->session_init = true; + // audio setting_cb and frame_cb + chiaki_opus_decoder_set_cb(&this->opus_decoder, InitAudioCB, AudioCB, user); + chiaki_opus_decoder_get_sink(&this->opus_decoder, &audio_sink); + chiaki_session_set_audio_sink(&(this->session), &audio_sink); + chiaki_session_set_video_sample_cb(&(this->session), VideoCB, user); + chiaki_session_set_event_cb(&(this->session), EventCB, user); + return 0; +} + +int Host::FiniSession() +{ + if(this->session_init) + { + chiaki_session_join(&this->session); + chiaki_session_fini(&this->session); + chiaki_opus_decoder_fini(&this->opus_decoder); + } + return 0; +} + +void Host::StopSession() +{ + chiaki_session_stop(&this->session); +} + +void Host::StartSession() +{ + ChiakiErrorCode err = chiaki_session_start(&this->session); + if(err != CHIAKI_ERR_SUCCESS) + { + chiaki_session_fini(&this->session); + throw Exception("Chiaki Session Start failed"); + } +} + +void Host::SendFeedbackState(ChiakiControllerState * state) +{ + // send controller/joystick key + chiaki_session_set_controller_state(&this->session, state); +} + +void Host::RegistCB(ChiakiRegistEvent * event) +{ + // Chiaki callback fuction + // fuction called by lib chiaki regist + // durring client pin code registration + // + // read data from lib and record secrets into Host object + + this->registered = false; + switch(event->type) + { + case CHIAKI_REGIST_EVENT_TYPE_FINISHED_CANCELED: + CHIAKI_LOGI(this->log, "Register event CHIAKI_REGIST_EVENT_TYPE_FINISHED_CANCELED"); + if(this->chiaki_regist_event_type_finished_canceled != nullptr) + { + this->chiaki_regist_event_type_finished_canceled(); + } + break; + case CHIAKI_REGIST_EVENT_TYPE_FINISHED_FAILED: + CHIAKI_LOGI(this->log, "Register event CHIAKI_REGIST_EVENT_TYPE_FINISHED_FAILED"); + if(this->chiaki_regist_event_type_finished_failed != nullptr) + { + this->chiaki_regist_event_type_finished_failed(); + } + break; + case CHIAKI_REGIST_EVENT_TYPE_FINISHED_SUCCESS: + { + ChiakiRegisteredHost *r_host = event->registered_host; + CHIAKI_LOGI(this->log, "Register event CHIAKI_REGIST_EVENT_TYPE_FINISHED_SUCCESS"); + // copy values form ChiakiRegisteredHost object + this->ap_ssid = r_host->ap_ssid; + this->ap_key = r_host->ap_key; + this->ap_name = r_host->ap_name; + memcpy( &(this->ps4_mac), &(r_host->ps4_mac), sizeof(this->ps4_mac) ); + this->ps4_nickname = r_host->ps4_nickname; + memcpy( &(this->rp_regist_key), &(r_host->rp_regist_key), sizeof(this->rp_regist_key) ); + this->rp_key_type = r_host->rp_key_type; + memcpy( &(this->rp_key), &(r_host->rp_key), sizeof(this->rp_key) ); + // mark host as registered + this->registered = true; + this->rp_key_data = true; + CHIAKI_LOGI(this->log, "Register Success %s", this->host_name.c_str()); + + if(this->chiaki_regist_event_type_finished_success != nullptr) + this->chiaki_regist_event_type_finished_success(); + + break; + } + } + // close registration socket + chiaki_regist_stop(&this->regist); + chiaki_regist_fini(&this->regist); +} + +bool Host::GetVideoResolution(int * ret_width, int * ret_height) +{ + switch(this->video_resolution) + { + case CHIAKI_VIDEO_RESOLUTION_PRESET_360p: + *ret_width = 640; + *ret_height = 360; + break; + case CHIAKI_VIDEO_RESOLUTION_PRESET_540p: + *ret_width = 950; + *ret_height = 540; + break; + case CHIAKI_VIDEO_RESOLUTION_PRESET_720p: + *ret_width = 1280; + *ret_height = 720; + break; + case CHIAKI_VIDEO_RESOLUTION_PRESET_1080p: + *ret_width = 1920; + *ret_height = 1080; + break; + default: + return false; + } + return true; +} + diff --git a/switch/src/io.cpp b/switch/src/io.cpp new file mode 100644 index 0000000..c5e12aa --- /dev/null +++ b/switch/src/io.cpp @@ -0,0 +1,797 @@ +/* + * 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 . + */ + +#ifdef __SWITCH__ +#include +#else +#include +#endif + +#include "io.h" + +// https://github.com/matlo/GIMX/blob/3af491c3b6a89c6a76c9831f1f022a1b73a00752/shared/gimxcontroller/include/ds4.h#L112 +#define DS4_TRACKPAD_MAX_X 1919 +#define DS4_TRACKPAD_MAX_Y 919 +#define SWITCH_TOUCHSCREEN_MAX_X 1280 +#define SWITCH_TOUCHSCREEN_MAX_Y 720 + +// source: +// https://github.com/thestr4ng3r/chiaki/blob/master/gui/src/avopenglwidget.cpp +// +// examples : +// https://www.roxlu.com/2014/039/decoding-h264-and-yuv420p-playback +// https://gist.github.com/roxlu/9329339 + +// use OpenGl to decode YUV +// the aim is to spare CPU load on nintendo switch + +static const char* shader_vert_glsl = R"glsl( +#version 150 core +in vec2 pos_attr; +out vec2 uv_var; +void main() +{ + uv_var = pos_attr; + gl_Position = vec4(pos_attr * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); +} +)glsl"; + +static const char *yuv420p_shader_frag_glsl = R"glsl( +#version 150 core +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(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, + 1.28033, -0.38059, 0.0) * yuv; + out_color = vec4(rgb, 1.0); +} +)glsl"; + +static const float vert_pos[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f +}; + +IO::IO(ChiakiLog * log) + : log(log) +{ +} + +IO::~IO() +{ + //FreeJoystick(); + if(this->sdl_audio_device_id <= 0) + { + SDL_CloseAudioDevice(this->sdl_audio_device_id); + } + FreeVideo(); +} + +void IO::SetMesaConfig() +{ + //TRACE("%s", "Mesaconfig"); + //setenv("MESA_GL_VERSION_OVERRIDE", "3.3", 1); + //setenv("MESA_GLSL_VERSION_OVERRIDE", "330", 1); + // Uncomment below to disable error checking and save CPU time (useful for production): + //setenv("MESA_NO_ERROR", "1", 1); +#ifdef DEBUG_OPENGL + // Uncomment below to enable Mesa logging: + setenv("EGL_LOG_LEVEL", "debug", 1); + setenv("MESA_VERBOSE", "all", 1); + setenv("NOUVEAU_MESA_DEBUG", "1", 1); + + // Uncomment below to enable shader debugging in Nouveau: + //setenv("NV50_PROG_OPTIMIZE", "0", 1); + setenv("NV50_PROG_DEBUG", "1", 1); + //setenv("NV50_PROG_CHIPSET", "0x120", 1); +#endif +} + +#ifdef DEBUG_OPENGL +#define D(x){ (x); CheckGLError(__func__, __FILE__, __LINE__); } +void IO::CheckGLError(const char* func, const char* file, int line) +{ + GLenum err; + while( (err = glGetError()) != GL_NO_ERROR ) + { + CHIAKI_LOGE(this->log, "glGetError: %x function: %s from %s line %d", err, func, file, line); + //GL_INVALID_VALUE, 0x0501 + // Given when a value parameter is not a legal value for that function. T + // his is only given for local problems; + // if the spec allows the value in certain circumstances, + // where other parameters or state dictate those circumstances, + // then GL_INVALID_OPERATION is the result instead. + } +} + +#define DS(x){ DumpShaderError(x, __func__, __FILE__, __LINE__); } +void IO::DumpShaderError(GLuint shader, const char* func, const char* file, int line) +{ + GLchar str[512+1]; + GLsizei len = 0; + glGetShaderInfoLog(shader, 512, &len, str); + if (len > 512) len = 512; + str[len] = '\0'; + CHIAKI_LOGE(this->log, "glGetShaderInfoLog: %s function: %s from %s line %d", str, func, file, line); +} + +#define DP(x){ DumpProgramError(x, __func__, __FILE__, __LINE__); } +void IO::DumpProgramError(GLuint prog, const char* func, const char* file, int line) +{ + GLchar str[512+1]; + GLsizei len = 0; + glGetProgramInfoLog(prog, 512, &len, str); + if (len > 512) len = 512; + str[len] = '\0'; + CHIAKI_LOGE(this->log, "glGetProgramInfoLog: %s function: %s from %s line %d", str, func, file, line); +} + +#else +// do nothing +#define D(x){ (x); } +#define DS(x){ } +#define DP(x){ } +#endif + + +bool IO::VideoCB(uint8_t * buf, size_t buf_size) +{ + // callback function to decode video buffer + + AVPacket packet; + av_init_packet(&packet); + packet.data = buf; + packet.size = buf_size; + AVFrame * frame = av_frame_alloc(); + if(!frame) + { + CHIAKI_LOGE(this->log, "UpdateFrame Failed to alloc AVFrame"); + av_packet_unref(&packet); + return false; + } + +send_packet: + // Push + int r = avcodec_send_packet(this->codec_context, &packet); + if(r != 0) + { + if(r == AVERROR(EAGAIN)) + { + CHIAKI_LOGE(this->log, "AVCodec internal buffer is full removing frames before pushing"); + r = avcodec_receive_frame(this->codec_context, frame); + // send decoded frame for sdl texture update + if(r != 0) + { + CHIAKI_LOGE(this->log, "Failed to pull frame"); + av_frame_free(&frame); + av_packet_unref(&packet); + return false; + } + goto send_packet; + } + else + { + char errbuf[128]; + av_make_error_string(errbuf, sizeof(errbuf), r); + CHIAKI_LOGE(this->log, "Failed to push frame: %s", errbuf); + av_frame_free(&frame); + av_packet_unref(&packet); + return false; + } + } + + this->mtx.lock(); + // Pull + r = avcodec_receive_frame(this->codec_context, this->frame); + this->mtx.unlock(); + + if(r != 0) + CHIAKI_LOGE(this->log, "Failed to pull frame"); + + av_frame_free(&frame); + av_packet_unref(&packet); + return true; +} + + +void IO::InitAudioCB(unsigned int channels, unsigned int rate) +{ + SDL_AudioSpec want, have, test; + SDL_memset(&want, 0, sizeof(want)); + + //source + //[I] Audio Header: + //[I] channels = 2 + //[I] bits = 16 + //[I] rate = 48000 + //[I] frame size = 480 + //[I] unknown = 1 + want.freq = rate; + want.format = AUDIO_S16SYS; + // 2 == stereo + want.channels = channels; + want.samples = 1024; + want.callback = NULL; + + if(this->sdl_audio_device_id <= 0) + { + // the chiaki session might be called many times + // open the audio device only once + this->sdl_audio_device_id = SDL_OpenAudioDevice(NULL, 0, &want, NULL, 0); + } + + if(this->sdl_audio_device_id <= 0) + { + CHIAKI_LOGE(this->log, "SDL_OpenAudioDevice failed: %s\n", SDL_GetError()); + } + else + { + SDL_PauseAudioDevice(this->sdl_audio_device_id, 0); + } +} + +void IO::AudioCB(int16_t * buf, size_t samples_count) +{ + //int az = SDL_GetQueuedAudioSize(host->audio_device_id); + // len the number of bytes (not samples!) to which (data) points + int success = SDL_QueueAudio(this->sdl_audio_device_id, buf, sizeof(int16_t)*samples_count*2); + if(success != 0) + CHIAKI_LOGE(this->log, "SDL_QueueAudio failed: %s\n", SDL_GetError()); +} + +bool IO::InitVideo(int video_width, int video_height, int screen_width, int screen_height) +{ + CHIAKI_LOGV(this->log, "load InitVideo"); + this->video_width = video_width; + this->video_height = video_height; + + this->screen_width = screen_width; + this->screen_height = screen_height; + this->frame = av_frame_alloc(); + + if(!InitAVCodec()) + { + throw Exception("Failed to initiate libav codec"); + } + + if(!InitOpenGl()) + { + throw Exception("Failed to initiate OpenGl"); + } + return true; +} + +void IO::EventCB(ChiakiEvent *event) +{ + switch(event->type) + { + case CHIAKI_EVENT_CONNECTED: + CHIAKI_LOGI(this->log, "EventCB CHIAKI_EVENT_CONNECTED"); + if(this->chiaki_event_connected_cb != nullptr) + this->quit = !this->chiaki_event_connected_cb(); + else + this->quit = false; + break; + case CHIAKI_EVENT_LOGIN_PIN_REQUEST: + CHIAKI_LOGI(this->log, "EventCB CHIAKI_EVENT_LOGIN_PIN_REQUEST"); + if(this->chiaki_even_login_pin_request_cb != nullptr) + this->quit = !this->chiaki_even_login_pin_request_cb(event->login_pin_request.pin_incorrect); + break; + case CHIAKI_EVENT_QUIT: + CHIAKI_LOGI(this->log, "EventCB CHIAKI_EVENT_QUIT"); + if(this->chiaki_event_quit_cb != nullptr) + this->quit = !this->chiaki_event_quit_cb(&event->quit); + else + this->quit = true; + break; + } +} + + +bool IO::FreeVideo() +{ + bool ret = true; + + if(this->frame) + av_frame_free(&this->frame); + + // avcodec_alloc_context3(codec); + if(this->codec_context) + { + avcodec_close(this->codec_context); + avcodec_free_context(&this->codec_context); + } + + return ret; +} + +bool IO::ReadUserKeyboard(char *buffer, size_t buffer_size) +{ +#ifdef CHIAKI_SWITCH_ENABLE_LINUX + // use cin to get user input from linux + std::cin.getline(buffer, buffer_size); + CHIAKI_LOGI(this->log, "Got user input: %s\n", buffer); +#else + // https://kvadevack.se/post/nintendo-switch-virtual-keyboard/ + SwkbdConfig kbd; + Result rc = swkbdCreate(&kbd, 0); + + if (R_SUCCEEDED(rc)) + { + swkbdConfigMakePresetDefault(&kbd); + rc = swkbdShow(&kbd, buffer, buffer_size); + + if (R_SUCCEEDED(rc)) + { + CHIAKI_LOGI(this->log, "Got user input: %s\n", buffer); + } + else + { + CHIAKI_LOGE(this->log, "swkbdShow() error: %u\n", rc); + return false; + } + swkbdClose(&kbd); + } + else + { + CHIAKI_LOGE(this->log, "swkbdCreate() error: %u\n", rc); + return false; + } +#endif + return true; +} + +bool IO::ReadGameTouchScreen(ChiakiControllerState *state) +{ +#ifdef __SWITCH__ + hidScanInput(); + int touch_count = hidTouchCount(); + bool ret = false; + if(!touch_count) + { + for(int i=0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++) + { + if(state->touches[i].id != -1) + { + state->touches[i].x = 0; + state->touches[i].y = 0; + state->touches[i].id = -1; + state->buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; // touchscreen release + // the state changed + ret = true; + } + } + return ret; + } + + touchPosition touch; + for(int i=0; i < touch_count && i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++) + { + hidTouchRead(&touch, i); + + // 1280×720 px (16:9) + // ps4 controller aspect ratio looks closer to 29:10 + uint16_t x = touch.px * (DS4_TRACKPAD_MAX_X / SWITCH_TOUCHSCREEN_MAX_X); + uint16_t y = touch.py * (DS4_TRACKPAD_MAX_Y / SWITCH_TOUCHSCREEN_MAX_Y); + + // use nintendo switch border's 5% to + if(x <= (SWITCH_TOUCHSCREEN_MAX_X * 0.05) || x >= (SWITCH_TOUCHSCREEN_MAX_X * 0.95) + || y <= (SWITCH_TOUCHSCREEN_MAX_Y * 0.05) || y >= (SWITCH_TOUCHSCREEN_MAX_Y * 0.95)) + { + state->buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; // touchscreen + // printf("CHIAKI_CONTROLLER_BUTTON_TOUCHPAD\n"); + } + else + { + state->buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; // touchscreen release + } + + state->touches[i].x = x; + state->touches[i].y = y; + state->touches[i].id = i; + // printf("[point_id=%d] px=%03d, py=%03d, dx=%03d, dy=%03d, angle=%03d\n", + // i, touch.px, touch.py, touch.dx, touch.dy, touch.angle); + ret = true; + } + return ret; +#else + return false; +#endif +} + +bool IO::ReadGameKeys(SDL_Event *event, ChiakiControllerState *state) +{ + // return true if an event changed (gamepad input) + + // TODO + // share vs PS button + // Gyro ? + // rumble ? + bool ret = true; + switch(event->type) + { + case SDL_JOYAXISMOTION: + if(event->jaxis.which == 0) + { + // left joystick + if(event->jaxis.axis == 0) + // Left-right movement + state->left_x = event->jaxis.value; + else if(event->jaxis.axis == 1) + // Up-Down movement + state->left_y = event->jaxis.value; + else if(event->jaxis.axis == 2) + // Left-right movement + state->right_x = event->jaxis.value; + else if(event->jaxis.axis == 3) + // Up-Down movement + state->right_y = event->jaxis.value; + else + ret = false; + } + else if (event->jaxis.which == 1) + { + // right joystick + if(event->jaxis.axis == 0) + // Left-right movement + state->right_x = event->jaxis.value; + else if(event->jaxis.axis == 1) + // Up-Down movement + state->right_y = event->jaxis.value; + else + ret = false; + } + else + ret = false; + break; + case SDL_JOYBUTTONDOWN: + // printf("Joystick %d button %d DOWN\n", + // event->jbutton.which, event->jbutton.button); + switch(event->jbutton.button) + { + case 0: state->buttons |= CHIAKI_CONTROLLER_BUTTON_MOON; break; // KEY_A + case 1: state->buttons |= CHIAKI_CONTROLLER_BUTTON_CROSS; break; // KEY_B + case 2: state->buttons |= CHIAKI_CONTROLLER_BUTTON_PYRAMID; break; // KEY_X + case 3: state->buttons |= CHIAKI_CONTROLLER_BUTTON_BOX; break; // KEY_Y + case 12: state->buttons |= CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT; break; // KEY_DLEFT + case 14: state->buttons |= CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT; break; // KEY_DRIGHT + case 13: state->buttons |= CHIAKI_CONTROLLER_BUTTON_DPAD_UP; break; // KEY_DUP + case 15: state->buttons |= CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN; break; // KEY_DDOWN + case 6: state->buttons |= CHIAKI_CONTROLLER_BUTTON_L1; break; // KEY_L + case 7: state->buttons |= CHIAKI_CONTROLLER_BUTTON_R1; break; // KEY_R + case 8: state->l2_state = 0xff; break; // KEY_ZL + case 9: state->r2_state = 0xff; break; // KEY_ZR + case 4: state->buttons |= CHIAKI_CONTROLLER_BUTTON_L3; break; // KEY_LSTICK + case 5: state->buttons |= CHIAKI_CONTROLLER_BUTTON_R3; break; // KEY_RSTICK + case 10: state->buttons |= CHIAKI_CONTROLLER_BUTTON_OPTIONS; break; // KEY_PLUS + // FIXME + // case 11: state->buttons |= CHIAKI_CONTROLLER_BUTTON_SHARE; break; // KEY_MINUS + case 11: state->buttons |= CHIAKI_CONTROLLER_BUTTON_PS; break; // KEY_MINUS + default: + ret = false; + } + break; + case SDL_JOYBUTTONUP: + // printf("Joystick %d button %d UP\n", + // event->jbutton.which, event->jbutton.button); + switch(event->jbutton.button) + { + case 0: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_MOON; break; // KEY_A + case 1: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_CROSS; break; // KEY_B + case 2: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_PYRAMID; break; // KEY_X + case 3: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_BOX; break; // KEY_Y + case 12: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT; break; // KEY_DLEFT + case 14: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT; break; // KEY_DRIGHT + case 13: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_DPAD_UP; break; // KEY_DUP + case 15: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN; break; // KEY_DDOWN + case 6: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_L1; break; // KEY_L + case 7: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_R1; break; // KEY_R + case 8: state->l2_state = 0x00; break; // KEY_ZL + case 9: state->r2_state = 0x00; break; // KEY_ZR + case 4: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_L3; break; // KEY_LSTICK + case 5: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_R3; break; // KEY_RSTICK + case 10: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_OPTIONS; break; // KEY_PLUS + //case 11: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_SHARE; break; // KEY_MINUS + case 11: state->buttons ^= CHIAKI_CONTROLLER_BUTTON_PS; break; // KEY_MINUS + default: + ret = false; + } + break; + default: + ret = false; + } + return ret; +} + +bool IO::InitAVCodec() +{ + CHIAKI_LOGV(this->log, "loading AVCodec"); + // set libav video context + this->codec = avcodec_find_decoder(AV_CODEC_ID_H264); + if(!this->codec) + throw Exception("H264 Codec not available"); + + this->codec_context = avcodec_alloc_context3(codec); + if(!this->codec_context) + throw Exception("Failed to alloc codec context"); + + // use rock88's mooxlight-nx optimization + // https://github.com/rock88/moonlight-nx/blob/698d138b9fdd4e483c998254484ccfb4ec829e95/src/streaming/ffmpeg/FFmpegVideoDecoder.cpp#L63 + // this->codec_context->skip_loop_filter = AVDISCARD_ALL; + this->codec_context->flags |= AV_CODEC_FLAG_LOW_DELAY; + this->codec_context->flags2 |= AV_CODEC_FLAG2_FAST; + // this->codec_context->flags2 |= AV_CODEC_FLAG2_CHUNKS; + this->codec_context->thread_type = FF_THREAD_SLICE; + this->codec_context->thread_count = 4; + + if(avcodec_open2(this->codec_context, this->codec, nullptr) < 0) + { + avcodec_free_context(&this->codec_context); + throw Exception("Failed to open codec context"); + } + return true; +} + +bool IO::InitOpenGl() +{ + CHIAKI_LOGV(this->log, "loading OpenGL"); + + if(!InitOpenGlShader()) + return false; + + if(!InitOpenGlTextures()) + return false; + + return true; +} + +bool IO::InitOpenGlTextures() +{ + CHIAKI_LOGV(this->log, "loading OpenGL textrures"); + + D(glGenTextures(PLANES_COUNT, this->tex)); + D(glGenBuffers(PLANES_COUNT, this->pbo)); + uint8_t uv_default[] = {0x7f, 0x7f}; + for(int i=0; i < PLANES_COUNT; i++) + { + D(glBindTexture(GL_TEXTURE_2D, this->tex[i])); + D(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + D(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + D(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + D(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + D(glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, i > 0 ? uv_default : nullptr)); + } + + D(glUseProgram(this->prog)); + // bind only as many planes as we need + const char *plane_names[] = {"plane1", "plane2", "plane3"}; + for(int i=0; i < PLANES_COUNT; i++) + D(glUniform1i(glGetUniformLocation(this->prog, plane_names[i]), i)); + + D(glGenVertexArrays(1, &this->vao)); + D(glBindVertexArray(this->vao)); + + D(glGenBuffers(1, &this->vbo)); + D(glBindBuffer(GL_ARRAY_BUFFER, this->vbo)); + D(glBufferData(GL_ARRAY_BUFFER, sizeof(vert_pos), vert_pos, GL_STATIC_DRAW)); + + D(glBindBuffer(GL_ARRAY_BUFFER, this->vbo)); + D(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr)); + D(glEnableVertexAttribArray(0)); + + D(glCullFace(GL_BACK)); + D(glEnable(GL_CULL_FACE)); + D(glClearColor(0.5, 0.5, 0.5, 1.0)); + return true; +} + +GLuint IO::CreateAndCompileShader(GLenum type, const char* source) +{ + GLint success; + GLchar msg[512]; + + GLuint handle; + D(handle = glCreateShader(type)); + if (!handle) + { + CHIAKI_LOGE(this->log, "%u: cannot create shader", type); + DP(this->prog); + } + + D(glShaderSource(handle, 1, &source, nullptr)); + D(glCompileShader(handle)); + D(glGetShaderiv(handle, GL_COMPILE_STATUS, &success)); + + if (!success) + { + D(glGetShaderInfoLog(handle, sizeof(msg), nullptr, msg)); + CHIAKI_LOGE(this->log, "%u: %s\n", type, msg); + D(glDeleteShader(handle)); + } + + return handle; +} + +bool IO::InitOpenGlShader() +{ + CHIAKI_LOGV(this->log, "loading OpenGl Shaders"); + + D(this->vert = CreateAndCompileShader(GL_VERTEX_SHADER, shader_vert_glsl)); + D(this->frag = CreateAndCompileShader(GL_FRAGMENT_SHADER, yuv420p_shader_frag_glsl)); + + D(this->prog = glCreateProgram()); + + D(glAttachShader(this->prog, this->vert)); + D(glAttachShader(this->prog, this->frag)); + D(glBindAttribLocation(this->prog, 0, "pos_attr")); + D(glLinkProgram(this->prog)); + + GLint success; + D(glGetProgramiv(this->prog, GL_LINK_STATUS, &success)); + if (!success) + { + char buf[512]; + glGetProgramInfoLog(this->prog, sizeof(buf), nullptr, buf); + CHIAKI_LOGE(this->log, "OpenGL link error: %s", buf); + return false; + } + + D(glDeleteShader(this->vert)); + D(glDeleteShader(this->frag)); + + return true; +} + +inline void IO::SetOpenGlYUVPixels(AVFrame * frame) +{ + D(glUseProgram(this->prog)); + + int planes[][3] = { + // { width_divide, height_divider, data_per_pixel } + { 1, 1, 1 }, // Y + { 2, 2, 1 }, // U + { 2, 2, 1 } // V + }; + + this->mtx.lock(); + for(int i = 0; i < PLANES_COUNT; i++) + { + int width = frame->width / planes[i][0]; + int height = frame->height / planes[i][1]; + int size = width * height * planes[i][2]; + uint8_t * buf; + + D(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->pbo[i])); + D(glBufferData(GL_PIXEL_UNPACK_BUFFER, size, nullptr, GL_STREAM_DRAW)); + D(buf = reinterpret_cast(glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT))); + if(!buf) + { + GLint data; + D(glGetBufferParameteriv(GL_PIXEL_UNPACK_BUFFER, GL_BUFFER_SIZE, &data)); + CHIAKI_LOGE(this->log, "AVOpenGLFrame failed to map PBO"); + CHIAKI_LOGE(this->log, "Info buf == %p. size %d frame %d * %d, divs %d, %d, pbo %d GL_BUFFER_SIZE %x", + buf, size, frame->width, frame->height, planes[i][0], planes[i][1], pbo[i], data); + continue; + } + + if(frame->linesize[i] == width) + { + // Y + memcpy(buf, frame->data[i], size); + } + else + { + // UV + for(int l=0; ldata[i] + frame->linesize[i] * l, + width * planes[i][2]); + } + D(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); + D(glBindTexture(GL_TEXTURE_2D, tex[i])); + D(glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr)); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + this->mtx.unlock(); + glFinish(); +} + +inline void IO::OpenGlDraw() +{ + glClear(GL_COLOR_BUFFER_BIT); + + // send to OpenGl + SetOpenGlYUVPixels(this->frame); + + //avcodec_flush_buffers(this->codec_context); + D(glBindVertexArray(this->vao)); + + for(int i=0; i< PLANES_COUNT; i++) + { + D(glActiveTexture(GL_TEXTURE0 + i)); + D(glBindTexture(GL_TEXTURE_2D, this->tex[i])); + } + + D(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); + D(glBindVertexArray(0)); + D(glFinish()); +} + +bool IO::InitJoystick() +{ + // https://github.com/switchbrew/switch-examples/blob/master/graphics/sdl2/sdl2-simple/source/main.cpp#L57 + // open CONTROLLER_PLAYER_1 and CONTROLLER_PLAYER_2 + // when railed, both joycons are mapped to joystick #0, + // else joycons are individually mapped to joystick #0, joystick #1, ... + for (int i = 0; i < SDL_JOYSTICK_COUNT; i++) + { + this->sdl_joystick_ptr[i] = SDL_JoystickOpen(i); + if (sdl_joystick_ptr[i] == nullptr) + { + CHIAKI_LOGE(this->log, "SDL_JoystickOpen: %s\n", SDL_GetError()); + return false; + } + } + return true; +} + +bool IO::FreeJoystick() +{ + for (int i = 0; i < SDL_JOYSTICK_COUNT; i++) + { + if(SDL_JoystickGetAttached(sdl_joystick_ptr[i])) + SDL_JoystickClose(sdl_joystick_ptr[i]); + } + return true; +} + +bool IO::MainLoop(ChiakiControllerState * state) +{ + D(glUseProgram(this->prog)); + + // handle SDL events + while(SDL_PollEvent(&this->sdl_event)) + { + this->ReadGameKeys(&this->sdl_event, state); + switch(this->sdl_event.type) + { + case SDL_QUIT: + return false; + } + } + + ReadGameTouchScreen(state); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + OpenGlDraw(); + + return !this->quit; +} + diff --git a/switch/src/main.cpp b/switch/src/main.cpp new file mode 100644 index 0000000..2043b12 --- /dev/null +++ b/switch/src/main.cpp @@ -0,0 +1,175 @@ +/* + * 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 . + */ + +// chiaki modules +#include +#include + +// discover and wakeup ps4 host +// from local network +#include "discoverymanager.h" +#include "settings.h" +#include "io.h" +#include "gui.h" + +#ifdef __SWITCH__ +#include +#else +bool appletMainLoop() +{ + return true; +} +#endif + +#ifndef CHIAKI_SWITCH_ENABLE_LINUX +#define CHIAKI_ENABLE_SWITCH_NXLINK 1 +#endif + +#ifdef __SWITCH__ +// use a custom nintendo switch socket config +// chiaki requiers many threads with udp/tcp sockets +static const SocketInitConfig g_chiakiSocketInitConfig = { + .bsdsockets_version = 1, + + .tcp_tx_buf_size = 0x8000, + .tcp_rx_buf_size = 0x10000, + .tcp_tx_buf_max_size = 0x40000, + .tcp_rx_buf_max_size = 0x40000, + + .udp_tx_buf_size = 0x40000, + .udp_rx_buf_size = 0x40000, + + .sb_efficiency = 8, + + .num_bsd_sessions = 16, + .bsd_service_type = BsdServiceType_User, +}; +#endif // __SWITCH__ + +#ifdef CHIAKI_ENABLE_SWITCH_NXLINK +static int s_nxlinkSock = -1; + +static void initNxLink() +{ + // use chiaki socket config initialization + if (R_FAILED(socketInitialize(&g_chiakiSocketInitConfig))) + return; + + s_nxlinkSock = nxlinkStdio(); + if (s_nxlinkSock >= 0) + printf("initNxLink"); + else + socketExit(); +} + +static void deinitNxLink() +{ + if (s_nxlinkSock >= 0) + { + close(s_nxlinkSock); + s_nxlinkSock = -1; + } +} +#endif // CHIAKI_ENABLE_SWITCH_NXLINK + +#ifdef __SWITCH__ +extern "C" void userAppInit() +{ +#ifdef CHIAKI_ENABLE_SWITCH_NXLINK + initNxLink(); +#endif + // to load gui resources + romfsInit(); + plInitialize(PlServiceType_User); + // load socket custom config + socketInitialize(&g_chiakiSocketInitConfig); + setsysInitialize(); +} + +extern "C" void userAppExit() +{ +#ifdef CHIAKI_ENABLE_SWITCH_NXLINK + deinitNxLink(); +#endif // CHIAKI_ENABLE_SWITCH_NXLINK + socketExit(); + /* Cleanup tesla required services. */ + hidsysExit(); + pmdmntExit(); + plExit(); + + /* Cleanup default services. */ + fsExit(); + hidExit(); + appletExit(); + setsysExit(); + smExit(); +} +#endif // __SWITCH__ + +int main(int argc, char* argv[]) +{ + // init chiaki lib + ChiakiLog log; +#if defined(CHIAKI_ENABLE_SWITCH_NXLINK) || defined(CHIAKI_SWITCH_ENABLE_LINUX) +#ifdef __SWITCH__ + chiaki_log_init(&log, CHIAKI_LOG_ALL ^ CHIAKI_LOG_VERBOSE, chiaki_log_cb_print, NULL); +#else + chiaki_log_init(&log, CHIAKI_LOG_ALL, chiaki_log_cb_print, NULL); +#endif +#else + // null log for switch version + chiaki_log_init(&log, 0, chiaki_log_cb_print, NULL); +#endif + + // load chiaki lib + CHIAKI_LOGI(&log, "Loading chaki lib"); + + ChiakiErrorCode err = chiaki_lib_init(); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(&log, "Chiaki lib init failed: %s\n", chiaki_error_string(err)); + return 1; + } + + CHIAKI_LOGI(&log, "Loading SDL audio / joystick"); + if(SDL_Init( SDL_INIT_AUDIO | SDL_INIT_JOYSTICK )) + { + CHIAKI_LOGE(&log, "SDL initialization failed: %s", SDL_GetError()); + return 1; + } + + // build sdl OpenGl and AV decoders graphical interface + IO io = IO(&log); // open Input Output class + + // manage ps4 setting discovery wakeup and registration + std::map hosts; + // create host objects form config file + Settings settings = Settings(&log, &hosts); + CHIAKI_LOGI(&log, "Read chiaki settings file"); + // FIXME use GUI for config + settings.ParseFile(); + Host * host = nullptr; + + DiscoveryManager discoverymanager = DiscoveryManager(&settings); + MainApplication app = MainApplication(&hosts, &settings, &discoverymanager, &io, &log); + app.Load(); + + CHIAKI_LOGI(&log, "Quit applet"); + SDL_Quit(); + return 0; +} + diff --git a/switch/src/settings.cpp b/switch/src/settings.cpp new file mode 100644 index 0000000..b15cc7e --- /dev/null +++ b/switch/src/settings.cpp @@ -0,0 +1,561 @@ +/* + * 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 "settings.h" + +Host * Settings::GetOrCreateHost(std::string *host_name) +{ + bool created = false; + // update of create Host instance + if(this->hosts->find(*host_name) == hosts->end()) + { + // create host if udefined + Host h = Host(this->log, this, *host_name); + this->hosts->emplace( *host_name, h); + created = true; + } + + Host *host = &(this->hosts->at(*host_name)); + if(created) + { + // copy default settings + // to the newly created host + this->SetPSNOnlineID(host, this->global_psn_online_id); + this->SetPSNAccountID(host, this->global_psn_account_id); + this->SetVideoResolution(host, this->global_video_resolution); + this->SetVideoFPS(host, this->global_video_fps); + } + return host; +} + + +Settings::ConfigurationItem Settings::ParseLine(std::string *line, std::string *value) +{ + Settings::ConfigurationItem ci; + std::smatch m; + for(auto it = re_map.begin(); it != re_map.end(); it++) + { + if(regex_search(*line, m, it->second)) + { + ci = it->first; + *value = m[1]; + return ci; + } + } + return UNKNOWN; +} + +ChiakiLog* Settings::GetLogger() +{ + return this->log; +} + +size_t Settings::GetB64encodeSize(size_t in) +{ + // calculate base64 buffer size after encode + return ((4 * in / 3) + 3) & ~3; +} + +std::string Settings::GetPSNOnlineID(Host * host) +{ + if(host == nullptr || host->psn_online_id.length() == 0 ) + return this->global_psn_online_id; + else + return host->psn_online_id; +} + +std::string Settings::GetPSNAccountID(Host * host) +{ + if(host == nullptr || host->psn_account_id.length() == 0 ) + return this->global_psn_account_id; + else + return host->psn_account_id; +} + +void Settings::SetPSNOnlineID(Host * host, std::string psn_online_id) +{ + if(host == nullptr) + this->global_psn_online_id = psn_online_id; + else + host->psn_online_id = psn_online_id; +} + +void Settings::SetPSNAccountID(Host * host, std::string psn_account_id) +{ + if(host == nullptr) + this->global_psn_account_id = psn_account_id; + else + host->psn_account_id = psn_account_id; +} + +std::string Settings::ResolutionPresetToString(ChiakiVideoResolutionPreset resolution) +{ + switch(resolution) + { + case CHIAKI_VIDEO_RESOLUTION_PRESET_360p: + return "360p"; + case CHIAKI_VIDEO_RESOLUTION_PRESET_540p: + return "540p"; + case CHIAKI_VIDEO_RESOLUTION_PRESET_720p: + return "720p"; + case CHIAKI_VIDEO_RESOLUTION_PRESET_1080p: + return "1080p"; + } + return "UNKNOWN"; +} + +std::string Settings::FPSPresetToString(ChiakiVideoFPSPreset fps) +{ + switch(fps) + { + case CHIAKI_VIDEO_FPS_PRESET_30: + return "30"; + case CHIAKI_VIDEO_FPS_PRESET_60: + return "60"; + } + return "UNKNOWN"; +} + +ChiakiVideoResolutionPreset Settings::StringToResolutionPreset(std::string value) +{ + if (value.compare("1080p") == 0) + return CHIAKI_VIDEO_RESOLUTION_PRESET_1080p; + else if (value.compare("720p") == 0) + return CHIAKI_VIDEO_RESOLUTION_PRESET_720p; + else if (value.compare("540p") == 0) + return CHIAKI_VIDEO_RESOLUTION_PRESET_540p; + else if (value.compare("360p") == 0) + return CHIAKI_VIDEO_RESOLUTION_PRESET_360p; + + // default + CHIAKI_LOGE(this->log, "Unable to parse String resolution: %s", + value.c_str()); + + return CHIAKI_VIDEO_RESOLUTION_PRESET_720p; +} + +ChiakiVideoFPSPreset Settings::StringToFPSPreset(std::string value) +{ + if (value.compare("60") == 0) + return CHIAKI_VIDEO_FPS_PRESET_60; + else if (value.compare("30") == 0) + return CHIAKI_VIDEO_FPS_PRESET_30; + + // default + CHIAKI_LOGE(this->log, "Unable to parse String fps: %s", + value.c_str()); + + return CHIAKI_VIDEO_FPS_PRESET_30; +} + +int Settings::FPSPresetToInt(ChiakiVideoFPSPreset fps) +{ + switch(fps) + { + case CHIAKI_VIDEO_FPS_PRESET_30: + return 30; + case CHIAKI_VIDEO_FPS_PRESET_60: + return 60; + } + return 0; +} + +int Settings::ResolutionPresetToInt(ChiakiVideoResolutionPreset resolution) +{ + switch(resolution) + { + case CHIAKI_VIDEO_RESOLUTION_PRESET_360p: + return 360; + case CHIAKI_VIDEO_RESOLUTION_PRESET_540p: + return 540; + case CHIAKI_VIDEO_RESOLUTION_PRESET_720p: + return 720; + case CHIAKI_VIDEO_RESOLUTION_PRESET_1080p: + return 1080; + } + return 0; +} + +ChiakiVideoResolutionPreset Settings::GetVideoResolution(Host * host) +{ + if(host == nullptr) + return this->global_video_resolution; + else + return host->video_resolution; +} + +ChiakiVideoFPSPreset Settings::GetVideoFPS(Host * host) +{ + if(host == nullptr) + return this->global_video_fps; + else + return host->video_fps; +} + +void Settings::SetVideoResolution(Host * host, ChiakiVideoResolutionPreset value) +{ + if(host == nullptr) + this->global_video_resolution = value; + else + host->video_resolution = value; +} + +void Settings::SetVideoResolution(Host * host, std::string value) +{ + ChiakiVideoResolutionPreset p = StringToResolutionPreset(value); + this->SetVideoResolution(host, p); +} + +void Settings::SetVideoFPS(Host * host, ChiakiVideoFPSPreset value) +{ + if(host == nullptr) + this->global_video_fps = value; + else + host->video_fps = value; +} + +void Settings::SetVideoFPS(Host * host, std::string value) +{ + ChiakiVideoFPSPreset p = StringToFPSPreset(value); + this->SetVideoFPS(host, p); +} + +#ifdef CHIAKI_ENABLE_SWITCH_OVERCLOCK +int Settings::GetCPUOverclock(Host * host) +{ + if(host == nullptr) + return this->global_cpu_overclock; + else + return host->cpu_overclock; +} + +void Settings::SetCPUOverclock(Host * host, int value) +{ + int oc = OC_1326; + if(value > OC_1580) + // max OC + oc = OC_1785; + else if(OC_1580 >= value && value > OC_1326) + oc = OC_1580; + else if(OC_1326 >= value && value > OC_1220) + oc = OC_1326; + else if(OC_1220 >= value && value > OC_1020) + oc = OC_1220; + else if(OC_1020 >= value) + // no overclock + // default nintendo switch value + oc = OC_1020; + if(host == nullptr) + this->global_cpu_overclock = oc; + else + host->cpu_overclock = oc; +} + +void Settings::SetCPUOverclock(Host * host, std::string value) +{ + int v = atoi(value.c_str()); + this->SetCPUOverclock(host, v); +} +#endif + +std::string Settings::GetHostIPAddr(Host * host) +{ + if(host != nullptr) + return host->host_addr; + else + CHIAKI_LOGE(this->log, "Cannot GetHostIPAddr from nullptr host"); + return ""; +} + +std::string Settings::GetHostName(Host * host) +{ + if(host != nullptr) + return host->host_name; + else + CHIAKI_LOGE(this->log, "Cannot GetHostName from nullptr host"); + return ""; +} + +int Settings::GetHostRPKeyType(Host * host) +{ + if(host != nullptr) + return host->rp_key_type; + + CHIAKI_LOGE(this->log, "Cannot GetHostRPKeyType from nullptr host"); + return 0; +} + + +bool Settings::SetHostRPKeyType(Host * host, std::string value) +{ + if(host != nullptr) + { + // TODO Check possible rp_type values + host->rp_key_type = std::atoi(value.c_str()); + return true; + } + return false; +} + +std::string Settings::GetHostRPKey(Host * host) +{ + if(host != nullptr) + { + if(host->rp_key_data || host->registered) + { + size_t rp_key_b64_sz = this->GetB64encodeSize(0x10); + char rp_key_b64[rp_key_b64_sz + 1] = {0}; + ChiakiErrorCode err; + err = chiaki_base64_encode( + host->rp_key, 0x10, + rp_key_b64, sizeof(rp_key_b64)); + + if(CHIAKI_ERR_SUCCESS == err) + return rp_key_b64; + else + CHIAKI_LOGE(this->log, "Failed to encode rp_key to base64"); + } + } + else + CHIAKI_LOGE(this->log, "Cannot GetHostRPKey from nullptr host"); + + return ""; +} + +std::string Settings::GetHostRPRegistKey(Host * host) +{ + if(host != nullptr) + { + if(host->rp_key_data || host->registered) + { + size_t rp_regist_key_b64_sz = this->GetB64encodeSize(CHIAKI_SESSION_AUTH_SIZE); + char rp_regist_key_b64[rp_regist_key_b64_sz + 1] = {0}; + ChiakiErrorCode err; + err = chiaki_base64_encode( + (uint8_t *) host->rp_regist_key, CHIAKI_SESSION_AUTH_SIZE, + rp_regist_key_b64, sizeof(rp_regist_key_b64)); + + if(CHIAKI_ERR_SUCCESS == err) + return rp_regist_key_b64; + else + CHIAKI_LOGE(this->log, "Failed to encode rp_regist_key to base64"); + } + } + else + CHIAKI_LOGE(this->log, "Cannot GetHostRPRegistKey from nullptr host"); + + return ""; +} + +bool Settings::SetHostRPKey(Host * host, std::string rp_key_b64) +{ + if(host != nullptr) + { + size_t rp_key_sz = sizeof(host->rp_key); + ChiakiErrorCode err = chiaki_base64_decode( + rp_key_b64.c_str(), rp_key_b64.length(), + host->rp_key, &rp_key_sz); + if(CHIAKI_ERR_SUCCESS != err) + CHIAKI_LOGE(this->log, "Failed to parse RP_KEY %s (it must be a base64 encoded)", rp_key_b64.c_str()); + else + return true; + } + else + CHIAKI_LOGE(this->log, "Cannot SetHostRPKey from nullptr host"); + + return false; +} + +bool Settings::SetHostRPRegistKey(Host * host, std::string rp_regist_key_b64) +{ + if(host != nullptr) + { + size_t rp_regist_key_sz = sizeof(host->rp_regist_key); + ChiakiErrorCode err = chiaki_base64_decode( + rp_regist_key_b64.c_str(), rp_regist_key_b64.length(), + (uint8_t*) host->rp_regist_key, &rp_regist_key_sz); + if(CHIAKI_ERR_SUCCESS != err) + CHIAKI_LOGE(this->log, "Failed to parse RP_REGIST_KEY %s (it must be a base64 encoded)", rp_regist_key_b64.c_str()); + else + return true; + } + else + CHIAKI_LOGE(this->log, "Cannot SetHostRPKey from nullptr host"); + + return false; +} + +void Settings::ParseFile() +{ + CHIAKI_LOGI(this->log, "Parse config file %s", this->filename); + std::fstream config_file; + config_file.open(this->filename, std::fstream::in); + std::string line; + std::string value; + bool rp_key_b, rp_regist_key_b, rp_key_type_b; + Host *current_host = nullptr; + if(config_file.is_open()) + { + CHIAKI_LOGV(this->log, "Config file opened"); + Settings::ConfigurationItem ci; + while(getline(config_file, line)) + { + CHIAKI_LOGV(this->log, "Parse config line `%s`", line.c_str()); + // for each line loop over config regex + ci = this->ParseLine(&line, &value); + switch(ci) + { + // got to next line + case UNKNOWN: CHIAKI_LOGV(this->log, "UNKNOWN config"); break; + case HOST_NAME: + CHIAKI_LOGV(this->log, "HOST_NAME %s", value.c_str()); + // current host is in context + current_host = this->GetOrCreateHost(&value); + // all following case will edit the current_host config + break; + case HOST_IP: + CHIAKI_LOGV(this->log, "HOST_IP %s", value.c_str()); + if(current_host != nullptr) + current_host->host_addr = value; + + // reset bool flags + rp_key_b=false; + rp_regist_key_b=false; + rp_key_type_b=false; + break; + case PSN_ONLINE_ID: + CHIAKI_LOGV(this->log, "PSN_ONLINE_ID %s", value.c_str()); + // current_host == nullptr + // means we are in global ini section + // update default setting + this->SetPSNOnlineID(current_host, value); + break; + case PSN_ACCOUNT_ID: + CHIAKI_LOGV(this->log, "PSN_ACCOUNT_ID %s", value.c_str()); + this->SetPSNAccountID(current_host, value); + break; + case RP_KEY: + CHIAKI_LOGV(this->log, "RP_KEY %s", value.c_str()); + if(current_host != nullptr) + rp_key_b = this->SetHostRPKey(current_host, value); + break; + case RP_KEY_TYPE: + CHIAKI_LOGV(this->log, "RP_KEY_TYPE %s", value.c_str()); + if(current_host != nullptr) + // TODO Check possible rp_type values + rp_key_type_b = this->SetHostRPKeyType(current_host, value); + break; + case RP_REGIST_KEY: + CHIAKI_LOGV(this->log, "RP_REGIST_KEY %s", value.c_str()); + if(current_host != nullptr) + rp_regist_key_b = this->SetHostRPRegistKey(current_host, value); + break; + case VIDEO_RESOLUTION: + this->SetVideoResolution(current_host, value); + break; + case VIDEO_FPS: + this->SetVideoFPS(current_host, value); + break; + } // ci switch + if(rp_key_b && rp_regist_key_b && rp_key_type_b) + // the current host contains rp key data + current_host->rp_key_data = true; + } // is_open + config_file.close(); + } + return; +} + +int Settings::WriteFile() +{ + std::fstream config_file; + CHIAKI_LOGI(this->log, "Write config file %s", this->filename); + // flush file (trunc) + // the config file is completely overwritten + config_file.open(this->filename, std::fstream::out | std::ofstream::trunc); + std::string line; + std::string value; + + if(this->hosts == nullptr) + return -1; + + if(config_file.is_open()) + { + // save global settings + CHIAKI_LOGD(this->log, "Write Global config file %s", this->filename); + + if(this->global_video_resolution) + config_file << "video_resolution = \"" + << this->ResolutionPresetToString(this->GetVideoResolution(nullptr)) + << "\"\n"; + + if(this->global_video_fps) + config_file << "video_fps = " + << this->FPSPresetToString(this->GetVideoFPS(nullptr)) + << "\n"; + + if(this->global_psn_online_id.length()) + config_file << "psn_online_id = \"" << this->global_psn_online_id << "\"\n"; + + if(this->global_psn_account_id.length()) + config_file << "psn_account_id = \"" << this->global_psn_account_id << "\"\n"; + + // write host config in file + // loop over all configured + for(auto it = this->hosts->begin(); it != this->hosts->end(); it++ ) + { + // first is std::string + // second is Host + CHIAKI_LOGD(this->log, "Write Host config file %s", it->first.c_str()); + + config_file << "[" << it->first << "]\n" + << "host_ip = \"" << it->second.host_addr << "\"\n"; + + if(it->second.video_resolution) + config_file << "video_resolution = \"" + << this->ResolutionPresetToString(this->GetVideoResolution(&it->second)) + << "\"\n"; + + if(it->second.video_fps) + config_file << "video_fps = " + << this->FPSPresetToString(this->GetVideoFPS(&it->second)) + << "\n"; + + if(it->second.psn_online_id.length()) + config_file << "psn_online_id = \"" << it->second.psn_online_id << "\"\n"; + + if(it->second.psn_account_id.length()) + config_file << "psn_account_id = \"" << it->second.psn_account_id << "\"\n"; + + if(it->second.rp_key_data || it->second.registered) + { + char rp_key_type[33] = { 0 }; + snprintf(rp_key_type, sizeof(rp_key_type), "%d", it->second.rp_key_type); + // save registered rp key for auto login + config_file << "rp_key = \"" << this->GetHostRPKey(&it->second) << "\"\n" + << "rp_regist_key = \"" << this->GetHostRPRegistKey(&it->second) << "\"\n" + << "rp_key_type = " << rp_key_type << "\n"; + } // + config_file << "\n"; + } // for host + } // is_open + config_file.close(); + return 0; +} + diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 14e7f28..a71988d 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -50,3 +50,74 @@ if(NOT CHIAKI_USE_SYSTEM_JERASURE) add_library(Jerasure::Jerasure ALIAS jerasure) endif() + +################## +# borealis +################## + +if(CHIAKI_ENABLE_SWITCH) + # 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}) + 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) + find_library(GLAPI glapi) + find_library(DRM_NOUVEAU drm_nouveau) + target_link_libraries(borealis + glfw + ${EGL} + ${GLAPI} + ${DRM_NOUVEAU}) +endif() diff --git a/third-party/borealis b/third-party/borealis new file mode 160000 index 0000000..205e97a --- /dev/null +++ b/third-party/borealis @@ -0,0 +1 @@ +Subproject commit 205e97ab45922fa7f5c9fa6a85d5d686cd50b669