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 0000000..cb515da Binary files /dev/null and b/switch/res/icon.jpg differ diff --git a/switch/res/inter b/switch/res/inter new file mode 120000 index 0000000..7a6e86a --- /dev/null +++ b/switch/res/inter @@ -0,0 +1 @@ +../../third-party/borealis/resources/inter \ No newline at end of file diff --git a/switch/res/material b/switch/res/material new file mode 120000 index 0000000..8200753 --- /dev/null +++ b/switch/res/material @@ -0,0 +1 @@ +../../third-party/borealis/resources/material \ No newline at end of file diff --git a/switch/res/settings-20px.svg b/switch/res/settings-20px.svg new file mode 120000 index 0000000..bee6675 --- /dev/null +++ b/switch/res/settings-20px.svg @@ -0,0 +1 @@ +../../gui/res/settings-20px.svg \ No newline at end of file diff --git a/switch/src/discoverymanager.cpp b/switch/src/discoverymanager.cpp new file mode 100644 index 0000000..a7f0ecd --- /dev/null +++ b/switch/src/discoverymanager.cpp @@ -0,0 +1,259 @@ +/* + * 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 +#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