diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 145256ab..eef5ef5d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,10 +13,22 @@ on: env: VCPKG_ROOT: "${{github.workspace}}/dependencies/vcpkg" VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' - + VCPKG_FORCE_DOWNLOADED_BINARIES: true + jobs: build-ubuntu: - runs-on: ubuntu-22.04 + continue-on-error: true + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x64 + - os: ubuntu-22.04-arm + arch: arm + + name: build-ubuntu-${{ matrix.arch }} + runs-on: ${{ matrix.os }} steps: - name: "Checkout repo" uses: actions/checkout@v4 @@ -41,11 +53,6 @@ jobs: sudo apt update -qq sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev - - name: "Setup cmake" - uses: jwlawson/actions-setup-cmake@v2 - with: - cmake-version: '3.29.0' - - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh @@ -78,11 +85,21 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: cemu-bin-linux-x64 + name: cemu-bin-linux-${{ matrix.arch }} path: ./bin/Cemu build-appimage: - runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x64 + - os: ubuntu-22.04-arm + arch: arm + + name: build-appimage-${{ matrix.arch }} + runs-on: ${{ matrix.os }} needs: build-ubuntu steps: - name: Checkout Upstream Repo @@ -90,7 +107,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: cemu-bin-linux-x64 + name: cemu-bin-linux-${{ matrix.arch }} path: bin - name: "Install system dependencies" @@ -102,12 +119,12 @@ jobs: run: | export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" export DEPLOY_GTK_VERSION=3 - dist/linux/appimage.sh + dist/linux/appimage.sh ${{ runner.arch }} - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: cemu-appimage-x64 + name: cemu-appimage-${{ matrix.arch }} path: artifacts build-windows: diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt b/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt index 229b7107..11d30848 100644 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt @@ -5,6 +5,7 @@ project(wxwidgets-example) add_executable(main WIN32 popup.cpp) find_package(wxWidgets REQUIRED) +target_compile_features(main PRIVATE cxx_std_11) target_compile_definitions(main PRIVATE ${wxWidgets_DEFINITIONS} "$<$:${wxWidgets_DEFINITIONS_DEBUG}>") target_include_directories(main PRIVATE ${wxWidgets_INCLUDE_DIRS}) target_link_libraries(main PRIVATE ${wxWidgets_LIBRARIES}) @@ -13,6 +14,7 @@ add_executable(main2 WIN32 popup.cpp) find_package(wxWidgets CONFIG REQUIRED) target_link_libraries(main2 PRIVATE wx::core wx::base) +target_compile_features(main2 PRIVATE cxx_std_11) option(USE_WXRC "Use the wxrc resource compiler" ON) if(USE_WXRC) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/fix-dark-mode-offset.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/fix-dark-mode-offset.patch deleted file mode 100644 index 9b8a979a..00000000 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/fix-dark-mode-offset.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp -index 2b9e0d7f7e..7eab8b37df 100644 ---- a/src/msw/listctrl.cpp -+++ b/src/msw/listctrl.cpp -@@ -3110,6 +3110,11 @@ bool HandleSubItemPrepaint(wxListCtrl* listctrl, LPNMLVCUSTOMDRAW pLVCD, HFONT h - - if ( it.iImage != -1 ) - { -+ if ( !listctrl->HasCheckBoxes() ) -+ { -+ rc.left -= 6; -+ } -+ - const int yImage = rc.top + ((rc.bottom - rc.top) / 2 - hImage / 2); - ImageList_Draw(himl, it.iImage, hdc, rc.left, yImage, - nmcd.uItemState & CDIS_SELECTED ? ILD_SELECTED -@@ -3235,7 +3240,7 @@ void HandleItemPaint(wxListCtrl* listctrl, LPNMLVCUSTOMDRAW pLVCD, HFONT hfont) - // do not draw item background colour under the checkbox/image - RECT rcIcon; - wxGetListCtrlItemRect(nmcd.hdr.hwndFrom, nmcd.dwItemSpec, LVIR_ICON, rcIcon); -- if ( !::IsRectEmpty(&rcIcon) ) -+ if ( !::IsRectEmpty(&rcIcon) && listctrl->HasCheckBoxes() ) - rc.left = rcIcon.right + listctrl->FromDIP(GAP_BETWEEN_CHECKBOX_AND_TEXT); - } - diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch index 6ec4db46..41d5492f 100644 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch @@ -1,39 +1,45 @@ diff --git a/build/cmake/init.cmake b/build/cmake/init.cmake -index f044d22d4d..48fc5ad072 100644 +index 4ed7b264ef..195d776aeb 100644 --- a/build/cmake/init.cmake +++ b/build/cmake/init.cmake -@@ -198,7 +198,7 @@ if(WIN32) - endif() - endif() - --if(WIN32_MSVC_NAMING) -+if(0) +@@ -201,11 +201,11 @@ endif() + wx_get_install_dir(include) + if(WIN32_MSVC_NAMING) if(wxBUILD_SHARED) - set(lib_suffix "_dll") +- set(lib_suffix "_dll") ++ # set(lib_suffix "_dll") else() +- set(lib_suffix "_lib") ++ # set(lib_suffix "_lib") + endif() +- set(wxPLATFORM_LIB_DIR "${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}${lib_suffix}") ++ # set(wxPLATFORM_LIB_DIR "${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}${lib_suffix}") + set(wxINSTALL_INCLUDE_DIR "${include_dir}") + else() + wx_get_flavour(lib_flavour "-") diff --git a/build/cmake/install.cmake b/build/cmake/install.cmake -index a373983043..2e1ace7bf9 100644 +index cbbefdebe9..8f2759d5ad 100644 --- a/build/cmake/install.cmake +++ b/build/cmake/install.cmake -@@ -63,7 +63,7 @@ else() - - install(DIRECTORY DESTINATION "bin") +@@ -66,7 +66,7 @@ else() + wx_get_install_platform_dir(runtime) + install(DIRECTORY DESTINATION "${runtime_dir}") install(CODE "execute_process( \ - COMMAND ${CMAKE_COMMAND} -E create_symlink \ + COMMAND ${CMAKE_COMMAND} -E copy \ - \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/wx/config/${wxBUILD_FILE_ID}\" \ - \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/wx-config\" \ + \"${CMAKE_INSTALL_PREFIX}/${library_dir}/wx/config/${wxBUILD_FILE_ID}\" \ + \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${runtime_dir}/wx-config\" \ )" diff --git a/build/cmake/utils/CMakeLists.txt b/build/cmake/utils/CMakeLists.txt -index 15f4339ef9..f93849e025 100644 +index 608351ac42..d8110f9148 100644 --- a/build/cmake/utils/CMakeLists.txt +++ b/build/cmake/utils/CMakeLists.txt -@@ -39,7 +39,7 @@ if(wxUSE_XRC) +@@ -40,7 +40,7 @@ if(wxUSE_XRC) # Don't use wx_install() here to preserve escaping. install(CODE "execute_process( \ - COMMAND ${CMAKE_COMMAND} -E create_symlink \ + COMMAND ${CMAKE_COMMAND} -E copy \ - \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/${wxrc_output_name}${EXE_SUFFIX}\" \ - \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/wxrc${EXE_SUFFIX}\" \ + \"${CMAKE_INSTALL_PREFIX}/${runtime_dir}/${wxrc_output_name}${EXE_SUFFIX}\" \ + \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${runtime_dir}/wxrc${EXE_SUFFIX}\" \ )" diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch index f76a4562..52f3a004 100644 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch @@ -1,13 +1,12 @@ diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in -index b251109..60cf762 100644 +index 2f3a735d92..c134900764 100644 --- a/build/cmake/wxWidgetsConfig.cmake.in +++ b/build/cmake/wxWidgetsConfig.cmake.in -@@ -1,5 +1,8 @@ - @PACKAGE_INIT@ +@@ -155,6 +155,7 @@ foreach(libname @wxLIB_TARGETS@) + endforeach() -+include(CMakeFindDependencyMacro) + include(CMakeFindDependencyMacro) +find_dependency(NanoSVG CONFIG) -+ - cmake_policy(PUSH) - # Set policies to prevent warnings - if(POLICY CMP0057) + + if(TARGET wx::wxnet AND @wxUSE_WEBREQUEST_CURL@) + # make sure CURL targets are available: diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake b/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake index 12491480..d9962958 100644 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO wxWidgets/wxWidgets - REF "v${VERSION}" - SHA512 8ad17582c4ba721ffe76ada4bb8bd7bc4b050491220aca335fd0506a51354fb789d5bc3d965f0f459dc81784d6427c88272e2acc2099cddf73730231b5a16f62 + REF "bfd436b" + SHA512 bd3fd6d0d0db3b6fa34eceae1119e21ffd2f62221dcd249f8b8b82a6e65d83a05101e4e1e4ca9b9c4d7937add73b113bb029b03b05d2c3d87d17c1922d800a24 HEAD_REF master PATCHES install-layout.patch @@ -12,9 +12,26 @@ vcpkg_from_github( fix-pcre2.patch gtk3-link-libraries.patch sdl2.patch - fix-dark-mode-offset.patch ) +# Submodule dependencies +vcpkg_from_github( + OUT_SOURCE_PATH lexilla_SOURCE_PATH + REPO wxWidgets/lexilla + REF "27c20a6ae5eebf418debeac0166052ed6fb653bc" + SHA512 7e5de7f664509473b691af8261fca34c2687772faca7260eeba5f2984516e6f8edf88c27192e056c9dda996e2ad2c20f6d1dff1c4bd2f3c0d74852cb50ca424a + HEAD_REF wx +) +file(COPY "${lexilla_SOURCE_PATH}/" DESTINATION "${SOURCE_PATH}/src/stc/lexilla") +vcpkg_from_github( + OUT_SOURCE_PATH scintilla_SOURCE_PATH + REPO wxWidgets/scintilla + REF "0b90f31ced23241054e8088abb50babe9a44ae67" + SHA512 db1f3007f4bd8860fad0817b6cf87980a4b713777025128cf5caea8d6d17b6fafe23fd22ff6886d7d5a420f241d85b7502b85d7e52b4ddb0774edc4b0a0203e7 + HEAD_REF wx +) +file(COPY "${scintilla_SOURCE_PATH}/" DESTINATION "${SOURCE_PATH}/src/stc/scintilla") + vcpkg_check_features( OUT_FEATURE_OPTIONS FEATURE_OPTIONS FEATURES @@ -52,14 +69,20 @@ if(VCPKG_TARGET_IS_WINDOWS) endif() endif() +if("webview" IN_LIST FEATURES AND VCPKG_LIBRARY_LINKAGE STREQUAL "static") + list(APPEND OPTIONS -DwxUSE_WEBVIEW_EDGE_STATIC=ON) +endif() + vcpkg_find_acquire_program(PKGCONFIG) # This may be set to ON by users in a custom triplet. -# The use of 'wxUSE_STL' and 'WXWIDGETS_USE_STD_CONTAINERS' (ON or OFF) are not API compatible -# which is why they must be set in a custom triplet rather than a port feature. +# The use of 'WXWIDGETS_USE_STD_CONTAINERS' (ON or OFF) is not API compatible +# which is why it must be set in a custom triplet rather than a port feature. +# For backwards compatibility, we also replace 'wxUSE_STL' (which no longer +# exists) with 'wxUSE_STD_STRING_CONV_IN_WXSTRING' which still exists and was +# set by `wxUSE_STL` previously. if(NOT DEFINED WXWIDGETS_USE_STL) set(WXWIDGETS_USE_STL OFF) - set(WXWIDGETS_USE_STD_STRING_CONV_IN_WXSTRING OFF) endif() if(NOT DEFINED WXWIDGETS_USE_STD_CONTAINERS) @@ -70,19 +93,18 @@ vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS ${FEATURE_OPTIONS} - -DwxUSE_STC=OFF -DwxUSE_REGEX=sys -DwxUSE_ZLIB=sys -DwxUSE_EXPAT=sys -DwxUSE_LIBJPEG=sys -DwxUSE_LIBPNG=sys -DwxUSE_LIBTIFF=sys - -DwxUSE_LIBWEBP=sys -DwxUSE_NANOSVG=sys + -DwxUSE_LIBWEBP=sys -DwxUSE_GLCANVAS=ON -DwxUSE_LIBGNOMEVFS=OFF -DwxUSE_LIBNOTIFY=OFF - -DwxUSE_STD_STRING_CONV_IN_WXSTRING=${WXWIDGETS_USE_STD_STRING_CONV_IN_WXSTRING} + -DwxUSE_STD_STRING_CONV_IN_WXSTRING=${WXWIDGETS_USE_STL} -DwxUSE_STD_CONTAINERS=${WXWIDGETS_USE_STD_CONTAINERS} -DwxUSE_UIACTIONSIMULATOR=OFF -DCMAKE_DISABLE_FIND_PACKAGE_GSPELL=ON @@ -101,7 +123,7 @@ vcpkg_cmake_configure( ) vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/wxWidgets) +vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/wxWidgets-3.3) # The CMake export is not ready for use: It lacks a config file. file(REMOVE_RECURSE @@ -126,6 +148,11 @@ vcpkg_copy_pdbs() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/include/msvc") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/lib/mswu") +if(VCPKG_BUILD_TYPE STREQUAL "release") + file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/mswud") +endif() + file(GLOB_RECURSE INCLUDES "${CURRENT_PACKAGES_DIR}/include/*.h") if(EXISTS "${CURRENT_PACKAGES_DIR}/lib/mswu/wx/setup.h") list(APPEND INCLUDES "${CURRENT_PACKAGES_DIR}/lib/mswu/wx/setup.h") @@ -191,12 +218,30 @@ endif() if("example" IN_LIST FEATURES) file(INSTALL - "${CMAKE_CURRENT_LIST_DIR}/example/CMakeLists.txt" - "${SOURCE_PATH}/samples/popup/popup.cpp" - "${SOURCE_PATH}/samples/sample.xpm" - DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/example" + "${CMAKE_CURRENT_LIST_DIR}/minimal_example/CMakeLists.txt" + "${SOURCE_PATH}/samples/minimal/minimal.cpp" + "${SOURCE_PATH}/samples/sample.xpm" + "${SOURCE_PATH}/samples/sample.rc" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/minimal_example" ) - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/example/popup.cpp" "../sample.xpm" "sample.xpm") + + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/minimal_example/minimal.cpp" "../sample.xpm" "sample.xpm") +endif() + +if("example" IN_LIST FEATURES) + file(INSTALL + "${CMAKE_CURRENT_LIST_DIR}/listctrl_example/CMakeLists.txt" + "${SOURCE_PATH}/samples/listctrl/listtest.cpp" + "${SOURCE_PATH}/samples/listctrl/listtest.h" + "${SOURCE_PATH}/samples/listctrl/listtest.rc" + "${SOURCE_PATH}/samples/sample.xpm" + "${SOURCE_PATH}/samples/sample.rc" + "${SOURCE_PATH}/samples/listctrl/bitmaps" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/listctrl_example" + ) + + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/listctrl_example/listtest.cpp" "../sample.xpm" "sample.xpm") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/listctrl_example/listtest.rc" "../sample.rc" "sample.rc") endif() configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/sdl2.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/sdl2.patch index 511775cc..cd6fa8d0 100644 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/sdl2.patch +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/sdl2.patch @@ -1,8 +1,8 @@ diff --git a/build/cmake/init.cmake b/build/cmake/init.cmake -index 5447d33..f5440b4 100644 +index 4ed7b264ef..9abf7c9d5d 100644 --- a/build/cmake/init.cmake +++ b/build/cmake/init.cmake -@@ -530,7 +530,9 @@ if(wxUSE_GUI) +@@ -667,7 +667,9 @@ if(wxUSE_GUI) endif() if(wxUSE_SOUND AND wxUSE_LIBSDL AND UNIX AND NOT APPLE) @@ -12,12 +12,12 @@ index 5447d33..f5440b4 100644 + set(SDL2_LIBRARY SDL2::SDL2 CACHE INTERNAL "") if(NOT SDL2_FOUND) find_package(SDL) - endif() + mark_as_advanced(SDL_INCLUDE_DIR SDLMAIN_LIBRARY) diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in -index 60cf762..202a8c3 100644 +index c134900764..7bcb691074 100644 --- a/build/cmake/wxWidgetsConfig.cmake.in +++ b/build/cmake/wxWidgetsConfig.cmake.in -@@ -2,6 +2,9 @@ +@@ -156,6 +156,9 @@ endforeach() include(CMakeFindDependencyMacro) find_dependency(NanoSVG CONFIG) @@ -25,5 +25,5 @@ index 60cf762..202a8c3 100644 + find_dependency(SDL2 CONFIG) +endif() - cmake_policy(PUSH) - # Set policies to prevent warnings + if(TARGET wx::wxnet AND @wxUSE_WEBREQUEST_CURL@) + # make sure CURL targets are available: diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake b/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake index 635b509b..bcbc0855 100644 --- a/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake @@ -9,7 +9,7 @@ set(WX_ROOT_DIR "${_vcpkg_wx_root}" CACHE INTERNAL "") unset(_vcpkg_wx_root) if(WIN32) - # Find all libs with "32" infix which is unknown to FindwxWidgets.cmake + # Find all libs with "33" infix which is unknown to FindwxWidgets.cmake function(z_vcpkg_wxwidgets_find_base_library BASENAME) find_library(WX_${BASENAME}d wx${BASENAME}33ud NAMES wx${BASENAME}d PATHS "${wxWidgets_ROOT_DIR}/debug/lib" NO_DEFAULT_PATH) find_library(WX_${BASENAME} wx${BASENAME}33u NAMES wx${BASENAME} PATHS "${wxWidgets_ROOT_DIR}/lib" NO_DEFAULT_PATH REQUIRED) diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index b66326d7..fbadd733 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -1,20 +1,22 @@ #!/bin/bash +if [ "$1" == 'X64' ]; then CPU_ARCH="x86_64"; else CPU_ARCH="aarch64"; fi + if [[ -z "${GITHUB_WORKSPACE}" ]]; then export GITHUB_WORKSPACE="." fi -curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" +curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-"${CPU_ARCH}".AppImage" chmod a+x linuxdeploy*.AppImage -curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous | grep "mkappimage-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2)" -o mkappimage.AppImage +curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous | grep "mkappimage-.*-"${CPU_ARCH}".AppImage" | head -n 1 | cut -d '"' -f 2)" -o mkappimage.AppImage chmod a+x mkappimage.AppImage curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh" chmod a+x linuxdeploy-plugin-gtk.sh curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh" chmod a+x linuxdeploy-plugin-checkrt.sh -if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then - sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh +if [[ ! -e /usr/lib/"${CPU_ARCH}"-linux-gnu ]]; then + sed -i 's#lib\/"${CPU_ARCH}"-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh fi mkdir -p AppDir/usr/bin @@ -32,11 +34,11 @@ cp -r bin/* AppDir/usr/share/Cemu mv AppDir/usr/share/Cemu/Cemu AppDir/usr/bin/ chmod +x AppDir/usr/bin/Cemu -cp /usr/lib/x86_64-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib +cp /usr/lib/"${CPU_ARCH}"-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync" export NO_STRIP=1 -./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \ +./linuxdeploy-"${CPU_ARCH}".AppImage --appimage-extract-and-run \ --appdir="${GITHUB_WORKSPACE}"/AppDir/ \ -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \ -i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \ @@ -54,4 +56,4 @@ echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-ho VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir mkdir -p "${GITHUB_WORKSPACE}"/artifacts/ -mv Cemu-"${GITVERSION}"-x86_64.AppImage "${GITHUB_WORKSPACE}"/artifacts/ +mv Cemu-"${GITVERSION}"-"${CPU_ARCH}".AppImage "${GITHUB_WORKSPACE}"/artifacts/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 04b6e56c..902bafcf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -138,7 +138,6 @@ target_link_libraries(CemuBin PRIVATE CemuGui CemuInput CemuUtil - OpenGL::GL SDL2::SDL2 ) diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 596d7139..4bb087fb 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -238,8 +238,6 @@ add_library(CemuCafe IOSU/legacy/iosu_acp.h IOSU/legacy/iosu_act.cpp IOSU/legacy/iosu_act.h - IOSU/legacy/iosu_boss.cpp - IOSU/legacy/iosu_boss.h IOSU/legacy/iosu_crypto.cpp IOSU/legacy/iosu_crypto.h IOSU/legacy/iosu_fpd.cpp @@ -252,6 +250,10 @@ add_library(CemuCafe IOSU/legacy/iosu_nim.h IOSU/nn/iosu_nn_service.cpp IOSU/nn/iosu_nn_service.h + IOSU/nn/boss/boss_common.cpp + IOSU/nn/boss/boss_common.h + IOSU/nn/boss/boss_service.cpp + IOSU/nn/boss/boss_service.h IOSU/PDM/iosu_pdm.cpp IOSU/PDM/iosu_pdm.h IOSU/ODM/iosu_odm.cpp @@ -418,6 +420,7 @@ add_library(CemuCafe OS/libs/nn_ccr/nn_ccr.h OS/libs/nn_cmpt/nn_cmpt.cpp OS/libs/nn_cmpt/nn_cmpt.h + OS/libs/nn_client_service.h OS/libs/nn_common.h OS/libs/nn_ec/nn_ec.cpp OS/libs/nn_ec/nn_ec.h @@ -618,28 +621,17 @@ else() endif() target_link_libraries(CemuCafe PRIVATE - CemuAudio CemuCommon - CemuComponents - CemuConfig - CemuGui - CemuInput - CemuResource - CemuUtil - imguiImpl - Boost::headers - Boost::nowide - CURL::libcurl - fmt::fmt - ${glslang_target} - ih264d - OpenSSL::Crypto - OpenSSL::SSL - PNG::PNG - pugixml::pugixml - ZArchive::zarchive - ZLIB::ZLIB - zstd::zstd + CemuGui + ZArchive::zarchive + imguiImpl + pugixml::pugixml + ZLIB::ZLIB + CURL::libcurl + ih264d + ${glslang_target} + PUBLIC + OpenSSL::SSL ) if (ENABLE_WAYLAND) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 49650017..e5ac4c98 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -33,10 +33,10 @@ #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/IOSU/legacy/iosu_mcp.h" #include "Cafe/IOSU/legacy/iosu_acp.h" -#include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" #include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" +#include "Cafe/IOSU/nn/boss/boss_service.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" @@ -561,6 +561,7 @@ namespace CafeSystem iosu::fpd::GetModule(), iosu::pdm::GetModule(), iosu::ccr_nfc::GetModule(), + iosu::boss::GetModule() }; // initialize all subsystems which are persistent and don't depend on a game running @@ -599,7 +600,6 @@ namespace CafeSystem iosu::iosuMcp_init(); iosu::mcp::Init(); iosu::iosuAcp_init(); - iosu::boss_init(); iosu::nim::Initialize(); iosu::odm::Initialize(); // init Cafe OS diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index 2636467b..99468801 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -27,11 +27,6 @@ struct LatteGPUState_t uint32 contextControl1; // optional features bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size - // draw context - struct - { - uint32 numInstances; - }drawContext; // stats uint32 frameCounter; uint32 flipCounter; // increased by one everytime a vsync + flip happens @@ -54,6 +49,8 @@ struct LatteGPUState_t // temporary (replace with proper solution later) bool tvBufferUsesSRGB; bool drcBufferUsesSRGB; + float tvGamma = 0.0f; + float drcGamma = 0.0f; // draw state bool activeShaderHasError; // if try, at least one currently bound shader stage has an error and cannot be used for drawing bool repeatTextureInitialization; // if set during rendertarget or texture initialization, repeat the process (textures likely have been invalidated) diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 4385cf49..963c49f7 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -62,16 +62,7 @@ public: { uint32 baseVertex = LatteGPUState.contextRegister[mmSQ_VTX_BASE_VTX_LOC]; uint32 baseInstance = LatteGPUState.contextRegister[mmSQ_VTX_START_INST_LOC]; - uint32 numInstances = LatteGPUState.drawContext.numInstances; - if (numInstances == 0) - return; - - /* - if (GetAsyncKeyState('B')) - { - cemuLog_force("[executeDraw] {} Count {} BaseVertex {} BaseInstance {}", m_isFirstDraw?"Init":"Fast", count, baseVertex, baseInstance); - } - */ + uint32 numInstances = LatteGPUState.contextNew.VGT_DMA_NUM_INSTANCES.get_NUM_INSTANCES(); if (!isAutoIndex) { @@ -391,7 +382,10 @@ LatteCMDPtr LatteCP_itIndexType(LatteCMDPtr cmd, uint32 nWords) LatteCMDPtr LatteCP_itNumInstances(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); - LatteGPUState.drawContext.numInstances = LatteReadCMD(); + uint32 numInstances = LatteReadCMD(); + if (numInstances == 0) + numInstances = 1; + LatteGPUState.contextNew.VGT_DMA_NUM_INSTANCES.set_NUM_INSTANCES(numInstances); return cmd; } @@ -690,9 +684,6 @@ LatteCMDPtr LatteCP_itDrawIndexAuto(LatteCMDPtr cmd, uint32 nWords, DrawPassCont cemu_assert_debug(nWords == 2); uint32 count = LatteReadCMD(); uint32 ukn = LatteReadCMD(); - - if (LatteGPUState.drawContext.numInstances == 0) - return cmd; LatteGPUState.currentDrawCallTick = GetTickCount(); // todo - better way to identify compute drawcalls if ((LatteGPUState.contextRegister[mmSQ_CONFIG] >> 24) == 0xE4) @@ -750,13 +741,7 @@ LatteCMDPtr LatteCP_itDrawImmediate(LatteCMDPtr cmd, uint32 nWords, DrawPassCont cemuLog_log(LogType::Force, "itDrawImmediate - Unsupported index type"); return cmd; } - // verify packet size - if (nWords != (2 + numIndexU32s)) - debugBreakpoint(); - - uint32 baseVertex = LatteGPUState.contextRegister[mmSQ_VTX_BASE_VTX_LOC]; - uint32 baseInstance = LatteGPUState.contextRegister[mmSQ_VTX_START_INST_LOC]; - uint32 numInstances = LatteGPUState.drawContext.numInstances; + cemu_assert_debug(nWords == (2 + numIndexU32s)); // verify packet size drawPassCtx.executeDraw(count, false, _tempIndexArrayMPTR); return cmd; diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp index b80bd869..48cfb0f2 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp @@ -120,16 +120,6 @@ uint8* LatteTextureLoader_getInputLinearOptimized(LatteTextureLoaderCtx* texture #define LatteTextureLoader_getInputLinearOptimized_(__textureLoader,__x,__y,__stepX,__stepY,__bpp,__sliceIndex,__numSlices,__sample,__pitch,__height) (textureLoader->inputData+((__x/__stepX) + __pitch * (__y/__stepY) + (__sliceIndex + __numSlices * __sample) * __height * __pitch)*(__bpp/8)) -float SRGB_to_RGB(float cs) -{ - float cl; - if (cs <= 0.04045f) - cl = cs / 12.92f; - else - cl = powf(((cs + 0.055f) / 1.055f), 2.4f); - return cl; -} - void decodeBC1Block(uint8* inputData, float* output4x4RGBA) { // read colors diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 00027900..743ab679 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -30,6 +30,7 @@ void Latte_LoadInitialRegisters() { LatteGPUState.contextNew.CB_TARGET_MASK.set_MASK(0xFFFFFFFF); LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.set_RESTART_INDEX(0xFFFFFFFF); + LatteGPUState.contextNew.VGT_DMA_NUM_INSTANCES.set_NUM_INSTANCES(1); LatteGPUState.contextRegister[Latte::REGADDR::PA_CL_CLIP_CNTL] = 0; *(float*)&LatteGPUState.contextRegister[mmDB_DEPTH_CLEAR] = 1.0f; } diff --git a/src/Cafe/HW/Latte/ISA/LatteReg.h b/src/Cafe/HW/Latte/ISA/LatteReg.h index 121d4e89..3fecaff6 100644 --- a/src/Cafe/HW/Latte/ISA/LatteReg.h +++ b/src/Cafe/HW/Latte/ISA/LatteReg.h @@ -445,6 +445,7 @@ namespace Latte VGT_DMA_INDEX_TYPE = 0xA29F, // todo - verify offset VGT_PRIMITIVEID_EN = 0xA2A1, + VGT_DMA_NUM_INSTANCES = 0xA2A2, VGT_MULTI_PRIM_IB_RESET_EN = 0xA2A5, @@ -977,6 +978,11 @@ float get_##__regname() const \ LATTE_BITFIELD_BOOL(PRIMITIVEID_EN, 0); }; + struct LATTE_VGT_DMA_NUM_INSTANCES : LATTEREG // 0xA2A2 + { + LATTE_BITFIELD_FULL_TYPED(NUM_INSTANCES, uint32); + }; + struct LATTE_VGT_MULTI_PRIM_IB_RESET_EN : LATTEREG // 0xA2A5 { LATTE_BITFIELD_BOOL(RESET_EN, 0); @@ -1541,7 +1547,7 @@ struct LatteContextRegister /* +0x28A7C */ Latte::LATTE_VGT_DMA_INDEX_TYPE VGT_DMA_INDEX_TYPE; /* +0x28A80 */ uint32 ukn28A80; /* +0x28A84 */ Latte::LATTE_VGT_PRIMITIVEID_EN VGT_PRIMITIVEID_EN; - /* +0x28A88 */ uint32 ukn28A88; + /* +0x28A88 */ Latte::LATTE_VGT_DMA_NUM_INSTANCES VGT_DMA_NUM_INSTANCES; /* +0x28A8C */ uint32 ukn28A8C; /* +0x28A90 */ uint32 ukn28A90; /* +0x28A94 */ Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_EN VGT_MULTI_PRIM_IB_RESET_EN; @@ -1629,6 +1635,7 @@ static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_TL) == Latte: static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_BR) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_BR * 4); static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_INDX) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX * 4); static_assert(offsetof(LatteContextRegister, VGT_PRIMITIVEID_EN) == Latte::REGADDR::VGT_PRIMITIVEID_EN * 4); +static_assert(offsetof(LatteContextRegister, VGT_DMA_NUM_INSTANCES) == Latte::REGADDR::VGT_DMA_NUM_INSTANCES * 4); static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_EN) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_EN * 4); static_assert(offsetof(LatteContextRegister, VGT_INSTANCE_STEP_RATE_0) == Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0 * 4); static_assert(offsetof(LatteContextRegister, VGT_INSTANCE_STEP_RATE_1) == Latte::REGADDR::VGT_INSTANCE_STEP_RATE_1 * 4); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 63c344ca..76f67692 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -603,7 +603,7 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu shader_unbind(RendererShader::ShaderType::kGeometry); shader_bind(shader->GetVertexShader()); shader_bind(shader->GetFragmentShader()); - shader->SetUniformParameters(*texView, {imageWidth, imageHeight}); + shader->SetUniformParameters(*texView, {imageWidth, imageHeight}, padView); // set viewport glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight); @@ -620,14 +620,12 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); texViewGL->samplerState.filterMag = 0xFFFFFFFF; - if ((!padView && !LatteGPUState.tvBufferUsesSRGB) || (padView && !LatteGPUState.drcBufferUsesSRGB)) - glDisable(GL_FRAMEBUFFER_SRGB); + glDisable(GL_FRAMEBUFFER_SRGB); uint16 indexData[6] = { 0,1,2,3,4,5 }; glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, indexData); - if ((!padView && !LatteGPUState.tvBufferUsesSRGB) || (padView && !LatteGPUState.drcBufferUsesSRGB)) - glEnable(GL_FRAMEBUFFER_SRGB); + glEnable(GL_FRAMEBUFFER_SRGB); // unbind texture texture_bindAndActivate(nullptr, 0); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp index cae53140..4d7fe2b9 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp @@ -227,6 +227,16 @@ sint32 RendererShaderGL::GetUniformLocation(const char* name) return glGetUniformLocation(m_program, name); } +void RendererShaderGL::SetUniform1i(sint32 location, sint32 value) +{ + glProgramUniform1i(m_program, location, value); +} + +void RendererShaderGL::SetUniform1f(sint32 location, float value) +{ + glProgramUniform1f(m_program, location, value); +} + void RendererShaderGL::SetUniform2fv(sint32 location, void* data, sint32 count) { glProgramUniform2fv(m_program, location, count, (const GLfloat*)data); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h index 60c51cc1..db69c14f 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h @@ -18,6 +18,9 @@ public: GLuint GetShaderObject() const { cemu_assert_debug(m_isCompiled); return m_shader_object; } sint32 GetUniformLocation(const char* name) override; + + void SetUniform1i(sint32 location, sint32 value) override; + void SetUniform1f(sint32 location, float value) override; void SetUniform2fv(sint32 location, void* data, sint32 count) override; void SetUniform4iv(sint32 location, void* data, sint32 count) override; diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index 9613d973..8c4dd5d7 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -1,9 +1,10 @@ #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "config/ActiveSettings.h" const std::string RendererOutputShader::s_copy_shader_source = R"( -void main() +void outputShader() { colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0); } @@ -62,7 +63,7 @@ vec4 bcFilter(vec2 uv, vec4 texelSize) mix(sample1, sample0, sx), sy); } -void main(){ +void outputShader(){ vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy); colorOut0 = vec4(bcFilter(passUV, texelSize).rgb,1.0); } @@ -172,7 +173,7 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize) return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y); } -void main(){ +void outputShader(){ vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy); colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0); } @@ -267,14 +268,20 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con m_uniformLocations[0].m_loc_textureSrcResolution = m_vertex_shader->GetUniformLocation("textureSrcResolution"); m_uniformLocations[0].m_loc_nativeResolution = m_vertex_shader->GetUniformLocation("nativeResolution"); m_uniformLocations[0].m_loc_outputResolution = m_vertex_shader->GetUniformLocation("outputResolution"); + m_uniformLocations[0].m_loc_applySRGBEncoding = m_vertex_shader->GetUniformLocation("applySRGBEncoding"); + m_uniformLocations[0].m_loc_targetGamma = m_fragment_shader->GetUniformLocation("targetGamma"); + m_uniformLocations[0].m_loc_displayGamma = m_fragment_shader->GetUniformLocation("displayGamma"); m_uniformLocations[1].m_loc_textureSrcResolution = m_fragment_shader->GetUniformLocation("textureSrcResolution"); m_uniformLocations[1].m_loc_nativeResolution = m_fragment_shader->GetUniformLocation("nativeResolution"); m_uniformLocations[1].m_loc_outputResolution = m_fragment_shader->GetUniformLocation("outputResolution"); + m_uniformLocations[1].m_loc_applySRGBEncoding = m_fragment_shader->GetUniformLocation("applySRGBEncoding"); + m_uniformLocations[1].m_loc_targetGamma = m_fragment_shader->GetUniformLocation("targetGamma"); + m_uniformLocations[1].m_loc_displayGamma = m_fragment_shader->GetUniformLocation("displayGamma"); } } -void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const +void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const { sint32 effectiveWidth, effectiveHeight; texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); @@ -300,6 +307,22 @@ void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_ res[1] = (float)output_res.y; shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1); } + + if (locations.m_loc_applySRGBEncoding != -1) + { + shader->SetUniform1i(locations.m_loc_applySRGBEncoding, padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB); + } + + if (locations.m_loc_targetGamma != -1) + { + shader->SetUniform1f(locations.m_loc_targetGamma, padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma()); + } + + if (locations.m_loc_displayGamma != -1) + { + shader->SetUniform1f(locations.m_loc_displayGamma, GetConfig().userDisplayGamma); + } + }; setUniforms(m_vertex_shader.get(), m_uniformLocations[0]); setUniforms(m_fragment_shader.get(), m_uniformLocations[1]); @@ -460,16 +483,52 @@ layout(push_constant) uniform pc { vec2 textureSrcResolution; vec2 nativeResolution; vec2 outputResolution; + bool applySRGBEncoding; // true = app requested sRGB encoding + float targetGamma; + float displayGamma; }; #else uniform vec2 textureSrcResolution; uniform vec2 nativeResolution; uniform vec2 outputResolution; +uniform bool applySRGBEncoding; +uniform float targetGamma; +uniform float displayGamma; #endif layout(location = 0) smooth in vec2 passUV; layout(binding = 0) uniform sampler2D textureSrc; layout(location = 0) out vec4 colorOut0; + +float sRGBEncode(float linear) +{ + if(linear <= 0.0031308) + return 12.92f * linear; + else + return 1.055f * pow(linear, 1.0f / 2.4f) - 0.055f; + +} + +vec3 sRGBEncode(vec3 linear) +{ + return vec3(sRGBEncode(linear.r), sRGBEncode(linear.g), sRGBEncode(linear.b)); +} + +// fwd. declaration +void outputShader(); +void main() +{ + outputShader(); // sets colorOut0 + if(applySRGBEncoding) + colorOut0 = vec4(sRGBEncode(colorOut0.rgb), 1.0f); + + if (displayGamma > 0.0f) + colorOut0 = pow(colorOut0, vec4(targetGamma / displayGamma) ); + else + colorOut0 = vec4( sRGBEncode( pow(colorOut0.rgb, vec3(targetGamma)) ), 1.0f); + +} + )" + shaderSrc; } void RendererOutputShader::InitializeStatic() diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h index a8f240d2..2322fc6a 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -17,7 +17,7 @@ public: RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source); virtual ~RendererOutputShader() = default; - void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const; + void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const; RendererShader* GetVertexShader() const { @@ -56,6 +56,9 @@ protected: sint32 m_loc_textureSrcResolution = -1; sint32 m_loc_nativeResolution = -1; sint32 m_loc_outputResolution = -1; + sint32 m_loc_applySRGBEncoding = -1; + sint32 m_loc_targetGamma = -1; + sint32 m_loc_displayGamma = -1; } m_uniformLocations[2]{}; private: diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.h b/src/Cafe/HW/Latte/Renderer/RendererShader.h index e3f254c6..b3d6d62b 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.h @@ -20,6 +20,8 @@ public: virtual sint32 GetUniformLocation(const char* name) = 0; + virtual void SetUniform1i(sint32 location, sint32 value) = 0; + virtual void SetUniform1f(sint32 location, float value) = 0; virtual void SetUniform2fv(sint32 location, void* data, sint32 count) = 0; virtual void SetUniform4iv(sint32 location, void* data, sint32 count) = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index a62741e4..80dcb4d7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -87,7 +87,7 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di if (vkCreateImage(m_vkr->GetLogicalDevice(), &imageInfo, nullptr, &vkObjTex->m_image) != VK_SUCCESS) m_vkr->UnrecoverableError("Failed to create texture image"); - if (m_vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + if (m_vkr->IsDebugMarkersEnabled()) { VkDebugUtilsObjectNameInfoEXT objName{}; objName.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 665a5da3..f6549e3c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -232,6 +232,16 @@ sint32 RendererShaderVk::GetUniformLocation(const char* name) return 0; } +void RendererShaderVk::SetUniform1i(sint32 location, sint32 value) +{ + cemu_assert_suspicious(); +} + +void RendererShaderVk::SetUniform1f(sint32 location, float value) +{ + cemu_assert_suspicious(); +} + void RendererShaderVk::SetUniform2fv(sint32 location, void* data, sint32 count) { cemu_assert_suspicious(); @@ -261,7 +271,7 @@ void RendererShaderVk::CreateVkShaderModule(std::span spirvBuffer) } // set debug name - if (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + if (vkr->IsDebugMarkersEnabled()) { VkDebugUtilsObjectNameInfoEXT objName{}; objName.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; @@ -282,8 +292,10 @@ void RendererShaderVk::FinishCompilation() void RendererShaderVk::CompileInternal(bool isRenderThread) { + const bool compileWithDebugInfo = ((VulkanRenderer*)g_renderer.get())->IsTracingToolEnabled(); + // try to retrieve SPIR-V module from cache - if (s_isLoadingShadersVk && (m_isGameShader && !m_isGfxPackShader) && s_spirvCache) + if (s_isLoadingShadersVk && (m_isGameShader && !m_isGfxPackShader) && s_spirvCache && !compileWithDebugInfo) { cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; @@ -319,21 +331,12 @@ void RendererShaderVk::CompileInternal(bool isRenderThread) Shader.setStrings(&cstr, 1); Shader.setEnvInput(glslang::EShSourceGlsl, state, glslang::EShClientVulkan, 100); Shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetClientVersion::EShTargetVulkan_1_1); - Shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetLanguageVersion::EShTargetSpv_1_3); - TBuiltInResource Resources = GetDefaultBuiltInResource(); std::string PreprocessedGLSL; - - VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get(); - - EShMessages messagesPreprocess; - if (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) - messagesPreprocess = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules | EShMsgDebugInfo); - else - messagesPreprocess = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); - glslang::TShader::ForbidIncluder Includer; + TBuiltInResource Resources = GetDefaultBuiltInResource(); + EShMessages messagesPreprocess = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); if (!Shader.preprocess(&Resources, 450, ENoProfile, false, false, messagesPreprocess, &PreprocessedGLSL, Includer)) { cemuLog_log(LogType::Force, fmt::format("GLSL Preprocessing Failed For {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); @@ -341,14 +344,9 @@ void RendererShaderVk::CompileInternal(bool isRenderThread) return; } - EShMessages messagesParseLink; - if (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) - messagesParseLink = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules | EShMsgDebugInfo); - else - messagesParseLink = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); - const char* PreprocessedCStr = PreprocessedGLSL.c_str(); Shader.setStrings(&PreprocessedCStr, 1); + EShMessages messagesParseLink = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); if (!Shader.parse(&Resources, 100, false, messagesParseLink)) { cemuLog_log(LogType::Force, fmt::format("GLSL parsing failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); @@ -382,9 +380,17 @@ void RendererShaderVk::CompileInternal(bool isRenderThread) glslang::SpvOptions spvOptions; spvOptions.disableOptimizer = false; - spvOptions.generateDebugInfo = (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT); spvOptions.validate = false; spvOptions.optimizeSize = true; + if (compileWithDebugInfo) + { + spvOptions.generateDebugInfo = true; + spvOptions.emitNonSemanticShaderDebugInfo = true; + spvOptions.emitNonSemanticShaderDebugSource = true; + + Shader.addSourceText(m_glslCode.c_str(), (uint32)m_glslCode.size()); + Shader.setSourceFile(fmt::format("shader_{:016x}_{:016x}.glsl", m_baseHash, m_auxHash).c_str()); + } //auto beginTime = benchmarkTimer_start(); @@ -392,8 +398,9 @@ void RendererShaderVk::CompileInternal(bool isRenderThread) //double timeDur = benchmarkTimer_stop(beginTime); //forceLogRemoveMe_printf("Shader GLSL-to-SPIRV compilation took %lfms Size %08x", timeDur, spirvBuffer.size()*4); - - if (s_spirvCache && m_isGameShader && m_isGfxPackShader == false) + + // store in cache, unless it got compiled with debug info or is a modified shader from a gfx pack + if (s_spirvCache && m_isGameShader && m_isGfxPackShader == false && !compileWithDebugInfo) { uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h index f9c3ede1..7b9fc34b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h @@ -32,6 +32,8 @@ public: static void Shutdown(); sint32 GetUniformLocation(const char* name) override; + void SetUniform1i(sint32 location, sint32 value) override; + void SetUniform1f(sint32 location, float value) override; void SetUniform2fv(sint32 location, void* data, sint32 count) override; void SetUniform4iv(sint32 location, void* data, sint32 count) override; VkShaderModule& GetShaderModule() { return m_shader_module; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 79bdffd1..b27aae0e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -319,18 +319,8 @@ VkSurfaceFormatKHR SwapchainInfoVk::ChooseSurfaceFormat(const std::vectorIsTracingToolEnabled()) + cemuLog_log(LogType::Force, "Debug: Tracing tool detected, will recompile all shaders with debug info enabled. This disables the SPIR-V cache."); + if (this->IsDebugMarkersEnabled()) + cemuLog_log(LogType::Force, "Debug: Detected tool capable of using debug markers, will use vkDebugMarkerSetObjectNameEXT to identify Vulkan objects"); // set initial viewport and scissor box size m_state.currentViewport.width = 4; @@ -612,6 +614,8 @@ VulkanRenderer::VulkanRenderer() m_uniformVarBufferMemoryIsCoherent = true; // unified memory else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; + else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + m_uniformVarBufferMemoryIsCoherent = true; else { memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory); @@ -624,7 +628,10 @@ VulkanRenderer::VulkanRenderer() m_uniformVarBufferPtr = (uint8*)bufferPtr; // texture readback buffer - memoryManager->CreateBuffer(TEXTURE_READBACK_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_textureReadbackBuffer, m_textureReadbackBufferMemory); + if (!memoryManager->CreateBuffer(TEXTURE_READBACK_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_textureReadbackBuffer, m_textureReadbackBufferMemory)) + { + memoryManager->CreateBuffer(TEXTURE_READBACK_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_textureReadbackBuffer, m_textureReadbackBufferMemory); + } bufferPtr = nullptr; vkMapMemory(m_logicalDevice, m_textureReadbackBufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); m_textureReadbackBufferPtr = (uint8*)bufferPtr; @@ -633,7 +640,10 @@ VulkanRenderer::VulkanRenderer() memoryManager->CreateBuffer(LatteStreamout_GetRingBufferSize(), VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | (m_featureControl.mode.useTFEmulationViaSSBO ? VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : 0), 0, m_xfbRingBuffer, m_xfbRingBufferMemory); // occlusion query result buffer - memoryManager->CreateBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); + if (!memoryManager->CreateBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults)) + { + memoryManager->CreateBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); + } bufferPtr = nullptr; vkMapMemory(m_logicalDevice, m_occlusionQueries.memoryQueryResults, 0, VK_WHOLE_SIZE, 0, &bufferPtr); m_occlusionQueries.ptrQueryResults = (uint64*)bufferPtr; @@ -1256,8 +1266,9 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME); - // check for framedebuggers - info.debugMarkersSupported = false; + // check for validation layers and frame debuggers + info.usingDebugMarkerTool = false; + info.usingTracingTool = false; if (info.deviceExtensions.tooling_info && vkGetPhysicalDeviceToolPropertiesEXT) { uint32_t toolCount = 0; @@ -1268,8 +1279,10 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, { for (auto& itr : toolProperties) { - if ((itr.purposes & VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT) != 0) - info.debugMarkersSupported = true; + if ((itr.purposes & VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT) != 0 && info.instanceExtensions.debug_utils && vkSetDebugUtilsObjectNameEXT) + info.usingDebugMarkerTool = true; + if ((itr.purposes & VK_TOOL_PURPOSE_TRACING_BIT) != 0) + info.usingTracingTool = true; } } } @@ -2590,7 +2603,6 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet uint64 hash = 0; hash += (uint64)vertexRendererShader; hash += (uint64)fragmentRendererShader; - hash += (uint64)(chainInfo.m_usesSRGB); hash += ((uint64)padView) << 1; const auto it = m_backbufferBlitPipelineCache.find(hash); @@ -2660,6 +2672,8 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, .offset = 0, .size = 3 * sizeof(float) * 2 // 3 vec2's + + 4 // + 1 VkBool32 + + 4 * 2 // + 2 float }; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; @@ -2771,10 +2785,6 @@ bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow) if(chainInfo.m_vsyncState != configValue) stateChanged = true; - const bool latteBufferUsesSRGB = mainWindow ? LatteGPUState.tvBufferUsesSRGB : LatteGPUState.drcBufferUsesSRGB; - if (chainInfo.m_usesSRGB != latteBufferUsesSRGB) - stateChanged = true; - int width, height; if (mainWindow) WindowSystem::GetWindowPhysSize(width, height); @@ -2799,7 +2809,6 @@ bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow) chainInfo.m_shouldRecreate = false; chainInfo.m_vsyncState = configValue; - chainInfo.m_usesSRGB = latteBufferUsesSRGB; return true; } @@ -3046,24 +3055,35 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr); + // update push constants - Vector2f pushData[3]; + struct + { + Vector2f vecs[3]; + VkBool32 applySRGBEncoding; + float targetGamma; + float displayGamma; + } pushData; // textureSrcResolution sint32 effectiveWidth, effectiveHeight; texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); - pushData[0] = {(float)effectiveWidth, (float)effectiveHeight}; + pushData.vecs[0] = {(float)effectiveWidth, (float)effectiveHeight}; // nativeResolution - pushData[1] = { + pushData.vecs[1] = { (float)texViewVk->baseTexture->width, (float)texViewVk->baseTexture->height, }; // outputResolution - pushData[2] = {(float)imageWidth,(float)imageHeight}; + pushData.vecs[2] = {(float)imageWidth,(float)imageHeight}; - vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float) * 2 * 3, &pushData); + pushData.applySRGBEncoding = padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB; + pushData.targetGamma = padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma(); + pushData.displayGamma = GetConfig().userDisplayGamma; + + vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pushData), &pushData); vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 5942f105..cae4bd10 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -481,7 +481,8 @@ private: uint32 nonCoherentAtomSize = 256; }limits; - bool debugMarkersSupported{ false }; // frame debugger is attached + bool usingDebugMarkerTool{ false }; // validation layer or other tool capable of handling debug markers is used + bool usingTracingTool{ false }; // frame debugger or other API replaying tool is used bool disableMultithreadedCompilation{ false }; // for old nvidia drivers }m_featureControl{}; @@ -937,7 +938,8 @@ private: public: bool GetDisableMultithreadedCompilation() const { return m_featureControl.disableMultithreadedCompilation; } bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; } - bool IsDebugUtilsEnabled() const { return m_featureControl.debugMarkersSupported && m_featureControl.instanceExtensions.debug_utils; } + bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; } + bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; } private: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 4534e03e..b41b7c37 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -245,7 +245,7 @@ void compilePipelineThread_queue(PipelineCompiler* v) bool VulkanRenderer::IsAsyncPipelineAllowed(uint32 numIndices) { // frame debuggers dont handle async well (as of 2020) - if (IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + if (IsTracingToolEnabled()) return false; CachedFBOVk* currentFBO = m_state.activeFBO; diff --git a/src/Cafe/IOSU/kernel/iosu_kernel.cpp b/src/Cafe/IOSU/kernel/iosu_kernel.cpp index 666e0373..3ea6d7da 100644 --- a/src/Cafe/IOSU/kernel/iosu_kernel.cpp +++ b/src/Cafe/IOSU/kernel/iosu_kernel.cpp @@ -659,7 +659,10 @@ namespace iosu for (uint32 i = 0; i < numIn + numOut; i++) { if (vec[i].baseVirt == nullptr && vec[i].size != 0) + { + cemuLog_logDebug(LogType::Force, "IPC Ioctlv failed because baseVirt is null but size is not 0"); return IOS_ERROR_INVALID; + } // todo - check for valid pointer range vec[i].basePhys = vec[i].baseVirt; } diff --git a/src/Cafe/IOSU/legacy/iosu_acp.cpp b/src/Cafe/IOSU/legacy/iosu_acp.cpp index 6a9e6b89..871cc495 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.cpp +++ b/src/Cafe/IOSU/legacy/iosu_acp.cpp @@ -782,7 +782,7 @@ namespace iosu public: AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {} - nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + nnResult ServiceCall(IPCServiceCall& serviceCall) override { cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main"); cemu_assert_unimplemented(); diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 413c1e68..de6ef9e0 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -603,7 +603,7 @@ namespace iosu public: ActService() : iosu::nn::IPCService("/dev/act") {} - nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + nnResult ServiceCall(IPCServiceCall& serviceCall) override { cemuLog_log(LogType::Force, "Unsupported service call to /dev/act"); cemu_assert_unimplemented(); diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp deleted file mode 100644 index 212d42a0..00000000 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ /dev/null @@ -1,1057 +0,0 @@ -#include "iosu_boss.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config/ActiveSettings.h" -#include "config/NetworkSettings.h" -#include "curl/curl.h" -#include "openssl/bn.h" -#include "openssl/x509.h" -#include "openssl/ssl.h" - -#include "iosu_ioctl.h" -#include "Cafe/OS/libs/nn_common.h" -#include "iosu_act.h" -#include "iosu_crypto.h" -#include "util/crypto/aes128.h" -#include "config/CemuConfig.h" - -#include "util/helpers/helpers.h" -#include "Cemu/ncrypto/ncrypto.h" -#include "Cafe/CafeSystem.h" - -namespace iosu -{ - enum TurnState - { - kUnknown = 0, - kStopped = 1, - kStoppedByPolicyList = 2, - kWaitTime = 3, - kWaitRun = 4, - kWaitResume = 5, - kRunning = 6, - kFinished = 7, - kSuccess = 16, - kError = 17, - }; - - enum class ContentType - { - kUnknownContent, - kXmlContent, - kBinaryFile, - kText, - }; - - enum class FileType - { - kUnknownFile, - kAppData, - }; - - enum TaskSettingType : uint32 - { - kRawDlTaskSetting = 0x10000698, - }; - - struct TaskFile - { - TaskFile(std::string file_name, uint32 data_id, FileType type, std::string url, uint32 size) - : file_name(file_name), data_id(data_id), file_type(type), url(url), size(size) {} - - std::string file_name; - uint32 data_id; - FileType file_type; - std::string url; - uint32 size; - uint64 last_modified = 0; - }; - - struct TaskSetting - { - static const uint32 kBossCode = 0x7C0; - static const uint32 kBossCodeLen = 0x20; - - static const uint32 kURL = 0x48; - static const uint32 kURLLen = 0x100; - - static const uint32 kClientCert = 0x41; - static const uint32 kCACert = 0x188; - - static const uint32 kServiceToken = 0x590; - static const uint32 kServiceTokenLen = 0x200; - - static const uint32 kDirectorySizeLimit = 0x7F0; - static const uint32 kDirectoryName = 0x7C8; - static const uint32 kDirectoryNameLen = 0x8; - - static const uint32 kFileName = 0x7D0; - static const uint32 kNbdlFileName = 0x7F8; - static const uint32 kFileNameLen = 0x20; - - std::array settings; - uint32be taskType; // +0x1000 - }; - - static_assert(sizeof(TaskSetting) == 0x1004, "sizeof(TaskSetting_t)"); - - struct Task - { - char task_id[8]{}; - uint32 account_id; - uint64 title_id; - - TaskSetting task_settings; - - uint32 exec_count = 0; - std::shared_ptr curl; - uint64 content_length = 0; - uint64 processed_length = 0; - - uint32 turn_state = 0; - uint32 wait_state = 0; - - long http_status_code = 0; - ContentType content_type = ContentType::kUnknownContent; - - std::vector result_buffer; - - std::queue queued_files; - std::vector file_buffer; - uint32 processed_file_size = 0; - - Task(const char* id, uint32 account_id, uint64 title_id, TaskSetting* settings) - { - strncpy(task_id, id, sizeof(task_id)); - this->account_id = account_id; - this->title_id = title_id; - this->task_settings.settings = settings->settings; - this->task_settings.taskType = settings->taskType; - - curl = std::shared_ptr(curl_easy_init(), curl_easy_cleanup); - if(GetConfig().proxy_server.GetValue() != "") - { - curl_easy_setopt(curl.get(), CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); - } - } - }; - -#define FAD_ENTRY_MAX_COUNT (512) - - struct BossStorageFadEntry - { - char name[0x20]; - uint32be fileNameId; - uint32 ukn24; - uint32 ukn28; - uint32 ukn2C; - uint32 ukn30; - uint32be timestampRelated; // guessed - }; - - static_assert(sizeof(BossStorageFadEntry) == 0x38, "sizeof(BossStorageFadEntry)"); - - struct BossStorageFadFile - { - uint8 _00[0x08]; - BossStorageFadEntry entries[FAD_ENTRY_MAX_COUNT]; - }; - - static_assert(sizeof(BossStorageFadFile) == 28680, "sizeof(BossStorageFadFile)"); - - struct BossNbdlHeader - { - /* +0x00 */ - uint32be magic; - /* +0x04 */ - uint32be version; // guessed - /* +0x08 */ - uint16be ukn08; // must always be 1 - /* +0x0A */ - uint16be ukn0A; // must always be 2 - /* +0x0C */ - uint8 nonce[0xC]; - /* +0x18 */ - uint32 padding18; // unused - /* +0x1C */ - uint32 padding1C; // unused - /* +0x20 */ - struct - { - uint8 uknHashData[0x20]; - } encryptedHeader; - } ; - - static_assert(sizeof(BossNbdlHeader) == 0x40, "BossNbdlHeader has invalid size"); - static_assert(offsetof(BossNbdlHeader, encryptedHeader) == 0x20, "offsetof(BossNbdlHeader, encryptedHeader)"); - - - struct - { - bool is_initialized; - - std::vector tasks; - } g_boss = {}; - - /* - X-BOSS-Closed - X-BOSS-TitleId - X-Boss-UniqueId - �\r���\r�(X-BOSS-Digest - - LOAD:E01038B4 0000000C C %susr/boss/ - LOAD:E0103A6C 00000011 C %susr/boss/%08x/ - LOAD:E0103B04 00000016 C %susr/boss/%08x/%08x/ - LOAD:E0103B1C 0000001B C %susr/boss/%08x/%08x/user/ - LOAD:E0103B5C 00000020 C %susr/boss/%08x/%08x/user/%08x/ - LOAD:E0103B38 00000022 C %susr/boss/%08x/%08x/user/common/ - LOAD:E0103AC4 00000020 C %susr/boss/%08x/%08x/user/temp/ - - LOAD:E01106DC 0000000A C /dev/boss - - LOAD:05063698 0000001C C /vol/storage_mlc01/usr/boss - LOAD:E2299CA8 00000028 C /vol/storage_mlc01/usr/save/system/boss - */ - - template - curl_slist* append_header_param(struct curl_slist* list, const char* format, TArgs&& ... args) - { - return curl_slist_append(list, fmt::format(fmt::runtime(format), std::forward(args)...).c_str()); - } - - bool starts_with(const char* str, const char* pre) - { - const size_t len_string = strlen(str); - const size_t len_prefix = strlen(pre); - return len_string < len_prefix ? false : strncmp(pre, str, len_prefix) == 0; - } - - size_t task_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) - { - Task* task = (Task*)userdata; - const size_t writeByteSize = size * nmemb; - - //if (task->result_buffer.size() - task->processed_length < writeByteSize) - // task->result_buffer.resize(task->result_buffer.size() + writeByteSize); - //writeByteSize = min(writeByteSize, task->result_buffer.capacity() - task->processed_length); - - //cemuLog_logDebug(LogType::Force, "task_write_callback: {} (processed: {})", writeByteSize, task->processed_length); - if (writeByteSize > 0) - { - //memcpy(task->result_buffer.data() + task->processed_length, ptr, writeByteSize); - task->result_buffer.insert(task->result_buffer.end(), ptr, ptr + writeByteSize); - task->processed_length += writeByteSize; - } - - return writeByteSize; - } - - size_t task_download_header_callback(char* ptr, size_t size, size_t nitems, void* userdata) - { - //cemuLog_logDebug(LogType::Force, "\tHeader: {}", ptr); - - return size * nitems; - } - - size_t task_download_filecallback(char* ptr, size_t size, size_t nmemb, void* userdata) - { - Task* task = (Task*)userdata; - const size_t writeByteSize = size * nmemb; - //writeByteSize = min(writeByteSize, task->file_buffer.capacity() - task->processed_file_size); - if (writeByteSize > 0) - { - //memcpy(task->file_buffer.data() + task->processed_file_size, ptr, writeByteSize); - task->file_buffer.insert(task->file_buffer.end(), ptr, ptr + writeByteSize); - task->processed_file_size += writeByteSize; - } - return writeByteSize; - } - - size_t task_header_callback(char* ptr, size_t size, size_t nitems, void* userdata) - { - Task* task = (Task*)userdata; - if (starts_with(ptr, "Content-Length: ")) - { - task->content_length = strtol(&ptr[16], nullptr, 0); - task->result_buffer.clear(); - task->result_buffer.reserve(task->content_length); - task->processed_length = 0; - } - else if (starts_with(ptr, "Content-Type: ")) - { - const char* type = &ptr[14]; - if (starts_with(type, "application/xml") || starts_with(type, "text/xml")) - task->content_type = ContentType::kXmlContent; - else if (starts_with(type, "x-application/octet-stream")) - task->content_type = ContentType::kBinaryFile; - else if (starts_with(type, "text/html")) - task->content_type = ContentType::kText; - else - { - cemuLog_logDebug(LogType::Force, "task_header_callback: unknown content type > {}", type); - } - } - else if (starts_with(ptr, "Last-Modified: ")) - { - // TODO timestamp (?) - } - - //cemuLog_logDebug(LogType::Force, "task_header_callback: len {} ({}) and type {}", task->content_length, task->result_buffer.capacity(), task->content_type); - //cemuLog_logDebug(LogType::Force, "\t{}", ptr); - return size * nitems; - } - - static CURLcode task_sslctx_function(CURL* curl, void* sslctx, void* param) - { - auto task_settings = (TaskSetting*)param; - if (task_settings->taskType == kRawDlTaskSetting) - { - cemuLog_logDebug(LogType::Force, "sslctx_function: adding client cert: {}", (int)task_settings->settings[TaskSetting::kClientCert]); - if (!iosuCrypto_addClientCertificate(sslctx, task_settings->settings[TaskSetting::kClientCert])) - assert_dbg(); - - uint32 location = TaskSetting::kCACert; - for (int i = 0; i < 3; ++i) - { - if (task_settings->settings[location] != 0) - { - cemuLog_logDebug(LogType::Force, "sslctx_function: adding ca cert: {}", (int)task_settings->settings[location]); - if (!iosuCrypto_addCACertificate(sslctx, task_settings->settings[location])) - { - cemuLog_log(LogType::Force, "Failed to load CA certificate file"); - assert_dbg(); - } - } - - location += TaskSetting::kCACert; - } - } - else - { - if (!iosuCrypto_addCACertificate(sslctx, 105)) - { - cemuLog_log(LogType::Force, "Failed to load certificate file"); - assert_dbg(); - } - - if (!iosuCrypto_addClientCertificate(sslctx, 3)) - { - cemuLog_log(LogType::Force, "Failed to load client certificate file"); - assert_dbg(); - } - } - - SSL_CTX_set_cipher_list((SSL_CTX*)sslctx, "AES256-SHA"); - // TLS_RSA_WITH_AES_256_CBC_SHA (in CURL it's called rsa_aes_256_sha) - SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); - SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); - return CURLE_OK; - } - - auto get_task(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = std::find_if(g_boss.tasks.begin(), g_boss.tasks.end(), [taskId, accountId, titleId](const Task& task) - { - return 0 == strncmp(taskId, task.task_id, sizeof(Task::task_id)) && accountId == task.account_id && titleId == task. - title_id; - }); - - return it; - } - - bool parse_xml_content(Task& task) - { - tinyxml2::XMLDocument doc; - //cemuLog_log(LogType::Force, (char*)task.result_buffer.data()); - if (doc.Parse((const char*)task.result_buffer.data(), task.processed_length) != tinyxml2::XML_SUCCESS) - return false; - - for (tinyxml2::XMLElement* sheet = doc.FirstChildElement("TaskSheet"); sheet; sheet = sheet-> - NextSiblingElement("TaskSheet")) - { - const auto files = sheet->FirstChildElement("Files"); - if (!files) - continue; - - for (tinyxml2::XMLElement* file = files->FirstChildElement("File"); file; file = file->NextSiblingElement("File")) - { - auto file_name = file->FirstChildElement("Filename"); - if (!file_name) - continue; - - auto data_id = file->FirstChildElement("DataId"); - if (!data_id) - continue; - - auto type = file->FirstChildElement("Type"); - if (!type) - continue; - - auto url = file->FirstChildElement("Url"); - if (!url) - continue; - - auto size = file->FirstChildElement("Size"); - if (!size) - continue; - - FileType file_type; - if (0 == strcmp(type->GetText(), "AppData")) - file_type = FileType::kAppData; - else - { - file_type = FileType::kUnknownFile; - } - - task.queued_files.emplace(file_name->GetText(), data_id->IntText(), file_type, url->GetText(), size->IntText()); - } - } - - return true; - } - - const uint64 kTimeStampConvertSeconds = 946684800ULL; - BossStorageFadEntry* boss_storage_fad_find_entry(BossStorageFadFile& fad_file, uint32 data_id) - { - for (auto& entry : fad_file.entries) - { - if (entry.fileNameId == data_id) - return &entry; - } - - return nullptr; - } - - void boss_storage_fad_append_or_update(BossStorageFadFile& fad_file, const char* name, uint32 data_id, uint64 timestamp) - { - for (auto& entry : fad_file.entries) - { - if (entry.fileNameId == 0 || strcmp(entry.name, name) == 0) - { - entry.fileNameId = data_id; - strcpy(entry.name, name); - entry.timestampRelated = (uint32)(timestamp - kTimeStampConvertSeconds); // time since 2000 - return; - } - } - } - - uint32 task_run(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - if (it == g_boss.tasks.cend()) - { - //it->turn_state = kError; - //it->wait_state = TRUE; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); - } - - if (!ActiveSettings::IsOnlineEnabled()) - { - it->turn_state = kError; - it->wait_state = TRUE; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - cemuLog_logDebug(LogType::Force, "task run state: {} | exec: {} (tasks: {})", it->turn_state, it->exec_count, g_boss.tasks.size()); - it->turn_state = kRunning; - it->exec_count++; - - /* - https://nppl.app.nintendo.net/p01/policylist/1/1/AT - https://npts.app.nintendo.net/p01/tasksheet/1/zvGSM4kO***kKnpT/schdat2?c=XX&l=en - https://npts.app.nintendo.net/p01/tasksheet/1/zvGSM4kO***kKnpT/optdat2?c=XX&l=en - https://npts.app.nintendo.net/p01/tasksheet/1/8UsM86l***jFk8z/wood1?c=XX&l=en - https://npts.app.nintendo.net/p01/tasksheet/1/8UsM86l***kjFk8z/woodBGM?c=XX&l=en - - - https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s/%s?c=%s&l=%s - https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s?c=%s&l=%s - 1 == version - bossCode - initFile - - */ - - uint32 turnstate = kSuccess; - struct curl_slist* list_headerParam = nullptr; - list_headerParam = append_header_param(list_headerParam, "X-BOSS-Digest"); // ??? - list_headerParam = append_header_param(list_headerParam, "X-Boss-UniqueId: {:05x}", ((titleId >> 8) & 0xFFFFF)); // %05x - list_headerParam = append_header_param(list_headerParam, "X-BOSS-TitleId: /usr/packages/title/{:016x}", titleId); // "/usr/packages/title/%016llx" - - CURL* curl = it->curl.get(); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 2); -#ifdef CEMU_DEBUG_ASSERT - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); - char errbuf[CURL_ERROR_SIZE]{}; - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); -#else - curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); -#endif - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, task_write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &(*it)); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it)); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C); - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - if (IsNetworkServiceSSLDisabled(ActiveSettings::GetNetworkService())) - { - curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L); - } - else - { - curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, task_sslctx_function); - curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &it->task_settings); - curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0); - } - - std::string requestUrl; - if(it->task_settings.taskType == kRawDlTaskSetting) - { - char serviceToken[TaskSetting::kServiceTokenLen]; - strncpy(serviceToken, (char*)&it->task_settings.settings[TaskSetting::kServiceToken], TaskSetting::kServiceTokenLen); - list_headerParam = append_header_param(list_headerParam, "X-Nintendo-ServiceToken: {}", serviceToken); - - char url[TaskSetting::kURLLen + 1]{}; - strncpy(url, (char*)&it->task_settings.settings[TaskSetting::kURL], TaskSetting::kURLLen); - requestUrl.assign(url); - } - else - { - char languageCode[8]; - switch (GetConfig().console_language) - { - case CafeConsoleLanguage::JA: - strcpy(languageCode, "ja"); - break; - case CafeConsoleLanguage::EN: - strcpy(languageCode, "en"); - break; - case CafeConsoleLanguage::FR: - strcpy(languageCode, "fr"); - break; - case CafeConsoleLanguage::DE: - strcpy(languageCode, "de"); - break; - case CafeConsoleLanguage::IT: - strcpy(languageCode, "it"); - break; - case CafeConsoleLanguage::ES: - strcpy(languageCode, "es"); - break; - case CafeConsoleLanguage::ZH: - strcpy(languageCode, "zh"); - break; - case CafeConsoleLanguage::KO: - strcpy(languageCode, "ko"); - break; - case CafeConsoleLanguage::NL: - strcpy(languageCode, "nl"); - break; - case CafeConsoleLanguage::PT: - strcpy(languageCode, "pt"); - break; - case CafeConsoleLanguage::RU: - strcpy(languageCode, "ru"); - break; - case CafeConsoleLanguage::TW: - strcpy(languageCode, "tw"); // usually zh-tw? - break; - default: - strcpy(languageCode, "en"); - break; - } - - const char* countryCode = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); - - char boss_code[0x20]; - strncpy(boss_code, (char*)&it->task_settings.settings[TaskSetting::kBossCode], TaskSetting::kBossCodeLen); - - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Pretendo: - requestUrl = PretendoURLs::BOSSURL; - break; - case NetworkService::Custom: - requestUrl = GetNetworkConfig().urls.BOSS.GetValue(); - break; - case NetworkService::Nintendo: - default: - requestUrl = NintendoURLs::BOSSURL; - break; - } - requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), "1", boss_code, it->task_id, countryCode, languageCode)); - } - - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list_headerParam); - curl_easy_setopt(curl, CURLOPT_URL, requestUrl.c_str()); - - int curl_result = curl_easy_perform(curl); - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &it->http_status_code); - static_assert(sizeof(it->http_status_code) == sizeof(long)); - - //it->turn_state = kFinished; - - curl_slist_free_all(list_headerParam); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr); - - if (curl_result != CURLE_OK) - { -#ifdef CEMU_DEBUG_ASSERT - cemuLog_logDebug(LogType::Force, "curl error buff: {}", errbuf); -#endif - it->turn_state = kError; - it->wait_state = TRUE; - cemuLog_logDebug(LogType::Force, "task_run curl fail: {}", curl_result); - return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); - } - else - { - if (it->http_status_code != 200) - { - cemuLog_logDebug(LogType::Force, "BOSS task_run: Received unexpected HTTP response code"); - } - if (it->http_status_code == 404) - { - // todo - is this correct behavior? - it->turn_state = kError; - it->wait_state = TRUE; - cemuLog_logDebug(LogType::Force, "task_run failed due to 404 error"); - return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); - } - } - - switch (it->content_type) - { - case ContentType::kXmlContent: - parse_xml_content(*it); - break; - case ContentType::kBinaryFile: - - break; - case ContentType::kText: - cemuLog_logDebug(LogType::Force, "task_run returns text: {}", fmt::ptr(it->result_buffer.data())); - break; - } - - - if (!it->queued_files.empty()) - { - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_download_header_callback); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, task_download_filecallback); - - std::string taskIdStr = it->task_id; - - try - { - fs::path path = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}", (uint32)(it->title_id >> 32), (uint32)(it->title_id & 0xFFFFFFFF), taskIdStr); - if (!fs::exists(path)) - fs::create_directories(path); - - char targetFileName[TaskSetting::kFileNameLen + 1]{}; - strncpy(targetFileName, (char*)&it->task_settings.settings[TaskSetting::kNbdlFileName], TaskSetting::kFileNameLen); - cemuLog_logDebug(LogType::Force, "\tnbdl task target filename: \"{}\"", targetFileName); - const bool hasFileName = targetFileName[0] != '\0'; - - while (!it->queued_files.empty()) - { - auto file = it->queued_files.front(); - it->queued_files.pop(); - // download only specific file - if (hasFileName && file.file_name != targetFileName) - continue; - - it->processed_file_size = 0; - it->file_buffer.clear(); - it->file_buffer.reserve(file.size); - - // create/open fad db content - BossStorageFadFile fad_content{}; - fs::path db_file = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", (uint32)(it->title_id >> 32), (uint32)(it->title_id & 0xFFFFFFFF), it->account_id, taskIdStr); - if (!fs::exists(db_file)) - fs::create_directories(db_file); - - db_file /= "fad.db"; - std::ifstream fad_file(db_file, std::ios::in | std::ios::binary); - if (fad_file.is_open()) - { - if (!fad_file.read((char*)&fad_content, sizeof(BossStorageFadFile))) - fad_content = {}; - - fad_file.close(); - } - - auto currentEntry = boss_storage_fad_find_entry(fad_content, file.data_id); - //TODO deep dive into IOSU to figure out how caching actually works on th Wii U - if(currentEntry && fs::exists(path / fmt::format(L"{:08x}", file.data_id))) - { - uint64 timestamp = (uint64)currentEntry->timestampRelated + kTimeStampConvertSeconds; - curl_easy_setopt(curl, CURLOPT_TIMEVALUE, timestamp); - curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); - } - else - { - curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 0); - curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); - } - - curl_easy_setopt(curl, CURLOPT_FILETIME, 1L); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, task_download_header_callback); - curl_easy_setopt(curl, CURLOPT_URL, file.url.c_str()); - curl_result = curl_easy_perform(curl); - if (curl_result != CURLE_OK) - { - cemuLog_logDebug(LogType::Force, "task_run curl failed on file download ({}): {} > {}", curl_result, file.file_name, file.url); - if (hasFileName) - { - turnstate = kError; - break; - } - else - continue; - } - - long unmet = 1; - const CURLcode result = curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &unmet); - if(result == CURLE_OK && unmet == 1) - { - // file is already up2date - if (hasFileName) - break; - else - continue; - } - - if(it->processed_file_size != file.size) - { - cemuLog_logDebug(LogType::Force, "task_run file download size mismatch: {} > {} > {} from {} bytes", file.file_name, file.url, it->processed_file_size, file.size); - if (hasFileName) - { - turnstate = kError; - break; - } - else - continue; - } - - uint64 filetime; - curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime); - - // dunno about that one - it->processed_length += it->processed_file_size; - it->content_length += file.size; - - // bossAesKey = OTP.XorKey ^ bossXor - // bossXor: 33 AC 6D 15 C2 26 0A 91 3B BF 73 C3 55 D8 66 04 - uint8 bossAesKey[16] = { 0x39,0x70,0x57,0x35,0x58,0x70,0x34,0x58,0x37,0x41,0x7a,0x30,0x71,0x5a,0x70,0x74 }; // "9pW5Xp4X7Az0qZpt" - - BossNbdlHeader* nbdlHeader = (BossNbdlHeader*)it->file_buffer.data(); - - if (nbdlHeader->magic != 'boss') - break; - if (nbdlHeader->version != 0x20001) - break; - if (nbdlHeader->ukn08 != 1) - break; - if (nbdlHeader->ukn0A != 2) - break; - - // file must be padded to 16 byte alignment for AES decryption (padding is cut off after decryption) - const uint32 file_size = (it->processed_file_size + 0xF) & (~0xF); - if (file_size != it->processed_file_size) - { - it->file_buffer.resize(file_size); - nbdlHeader = (BossNbdlHeader*)it->file_buffer.data(); - } - - // prepare nonce for AES128-CTR - uint8 aesNonce[0x10]; - memset(aesNonce, 0, sizeof(aesNonce)); - memcpy(aesNonce, nbdlHeader->nonce, 0xC); - aesNonce[0xF] = 1; - - // decrypt header - AES128CTR_transform(it->file_buffer.data() + offsetof(BossNbdlHeader, encryptedHeader), sizeof(BossNbdlHeader::encryptedHeader), bossAesKey, aesNonce); - // decrypt everything else - AES128CTR_transform(it->file_buffer.data() + sizeof(BossNbdlHeader), file_size - sizeof(BossNbdlHeader), bossAesKey, aesNonce); - - try - { - // create file with content - fs::path file_path = path / fmt::format(L"{:08x}", file.data_id); - std::ofstream new_file(file_path, std::ios::out | std::ios::binary | std::ios::trunc); - new_file.write((char*)it->file_buffer.data() + sizeof(BossNbdlHeader), it->processed_file_size - sizeof(BossNbdlHeader)); - new_file.flush(); - new_file.close(); - - - boss_storage_fad_append_or_update(fad_content, file.file_name.c_str(), file.data_id, filetime); - std::ofstream fad_file_updated(db_file, std::ios::out | std::ios::binary | std::ios::trunc); - fad_file_updated.write((char*)&fad_content, sizeof(BossStorageFadFile)); - fad_file_updated.flush(); - fad_file_updated.close(); - } - catch (const std::exception& ex) - { - cemuLog_logDebug(LogType::Force, "file error: {}", ex.what()); - } - - if (hasFileName) - break; - } - } - catch (const std::exception& ex) - { - cemuLog_logDebug(LogType::Force, "dir error: {}", ex.what()); - } - } - - - if (it->task_settings.taskType == kRawDlTaskSetting) - { - char directoryName[TaskSetting::kDirectoryNameLen + 1]{}; - if (it->task_settings.settings[TaskSetting::kDirectoryName] != '\0') - strncpy(directoryName, (char*)&it->task_settings.settings[TaskSetting::kDirectoryName], TaskSetting::kDirectoryNameLen); - else - strncpy(directoryName, it->task_id, TaskSetting::kDirectoryNameLen); - - char fileName[TaskSetting::kFileNameLen + 1]{}; - strncpy(fileName, (char*)&it->task_settings.settings[TaskSetting::kFileName], TaskSetting::kFileNameLen); - - // mcl01\usr\boss\00050000\1018dd00\user\\\ - fs::path path = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/{:08x}", (uint32)(it->title_id >> 32), - (uint32)(it->title_id & 0xFFFFFFFF), iosuAct_getAccountIdOfCurrentAccount()); - path /= directoryName; - - if (!fs::exists(path)) - fs::create_directories(path); - - path /= fileName; - - std::ofstream file(path); - if (file.is_open()) - { - file.write((char*)it->result_buffer.data(), it->result_buffer.size()); - file.flush(); - file.close(); - } - } - - it->turn_state = turnstate; - it->wait_state = TRUE; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - - bool task_is_registered(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend(); - } - - bool task_wait(const char* taskId, uint32 accountId, uint64 titleId, uint32 wait_state, uint32 timeout = 0) - { - const auto it = get_task(taskId, accountId, titleId); - if (it == g_boss.tasks.cend()) - { - return false; - } - - const auto start = tick_cached(); - while (it->wait_state != wait_state) - { - if (timeout != 0 && (uint32)std::chrono::duration_cast(tick_cached() - start).count() >= timeout) - { - cemuLog_logDebug(LogType::Force, "task_wait: timeout reached -> {} seconds passed", timeout); - return false; - } - - std::this_thread::yield(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - return true; - } - - uint32 task_register(const char* taskId, uint32 accountId, uint64 titleId, void* settings) - { - g_boss.tasks.emplace_back(taskId, accountId, titleId, (TaskSetting*)settings); - g_boss.tasks[g_boss.tasks.size() - 1].turn_state = kWaitTime; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - uint32 task_register_immediate_run(const char* taskId, uint32 accountId, uint64 titleId, void* settings) - { - g_boss.tasks.emplace_back(taskId, accountId, titleId, (TaskSetting*)settings); - g_boss.tasks[g_boss.tasks.size() - 1].turn_state = kWaitRun; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - void task_unregister(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - if (it != g_boss.tasks.cend()) - g_boss.tasks.erase(it); - } - - std::pair task_get_content_length(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->content_length) : std::make_pair(0u, (uint64)0); - } - - std::pair task_get_processed_length(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->processed_length) : std::make_pair(0u, (uint64)0); - } - - std::pair task_get_http_status_code(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->http_status_code) : std::make_pair(0u, (long)0); - } - - std::pair task_get_turn_state(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->turn_state) : std::make_pair(0u, (uint32)0); - } - - uint32 task_stop_scheduling(const char* task_id, uint32 account_id, uint64 title_id) - { - const auto it = get_task(task_id, account_id, title_id); - if (it != g_boss.tasks.cend()) - { - it->turn_state = kStopped; - // todo actually cancel the task if currently running (curl call) - // curl_easy_pause() -> resume on start scheduling if paused - } - - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - void boss_thread() - { - SetThreadName("boss_thread"); - while (true) - { - const uint32 return_value = 0; // Ioctl return value - - ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_BOSS); - if (ioQueueEntry->request == IOSU_BOSS_REQUEST_CEMU) - { - iosuBossCemuRequest_t* cemu_request = (iosuBossCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); - cemu_request->returnCode = 0; - - uint64 title_id; - if (cemu_request->titleId == 0) - title_id = CafeSystem::GetForegroundTitleId(); - else - title_id = cemu_request->titleId; - - uint32 account_id; - if (cemu_request->accountId == 0) - account_id = iosuAct_getAccountIdOfCurrentAccount(); - else - account_id = cemu_request->accountId; - - if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_RUN) - { - cemu_request->returnCode = task_run(cemu_request->taskId, account_id, title_id); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH) - { - auto result = task_get_content_length(cemu_request->taskId, account_id, title_id); - cemu_request->u64.exec_count = std::get<0>(result); - cemu_request->u64.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH) - { - auto result = task_get_processed_length(cemu_request->taskId, account_id, title_id); - cemu_request->u64.exec_count = std::get<0>(result); - cemu_request->u64.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE) - { - auto result = task_get_http_status_code(cemu_request->taskId, account_id, title_id); - cemu_request->u32.exec_count = std::get<0>(result); - cemu_request->u32.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_TURN_STATE) - { - auto result = task_get_turn_state(cemu_request->taskId, account_id, title_id); - cemu_request->u32.exec_count = std::get<0>(result); - cemu_request->u32.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_WAIT) - { - cemu_request->returnCode = task_wait(cemu_request->taskId, account_id, title_id, cemu_request->waitState, - cemu_request->timeout); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_REGISTER) - { - cemu_request->returnCode = task_register(cemu_request->taskId, account_id, title_id, cemu_request->settings); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_IS_REGISTERED) - { - cemu_request->returnCode = task_is_registered(cemu_request->taskId, account_id, title_id) ? TRUE : FALSE; - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN) - { - cemu_request->returnCode = task_register_immediate_run(cemu_request->taskId, account_id, title_id, - cemu_request->settings); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_UNREGISTER) - { - task_unregister(cemu_request->taskId, account_id, title_id); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_START_SCHEDULING) - { - // we just run it no matter what - //if(cemu_request->bool_parameter) - cemu_request->returnCode = task_run(cemu_request->taskId, account_id, title_id); - /*else - { - const auto it = get_task(cemu_request->taskId, account_id, title_id); - if (it != g_boss.tasks.cend()) - { - it->turn_state = kWaitRun; - } - }*/ - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_STOP_SCHEDULING) - { - cemu_request->returnCode = task_stop_scheduling(cemu_request->taskId, account_id, title_id); - } - else - assert_dbg(); - } - else - assert_dbg(); - - iosuIoctl_completeRequest(ioQueueEntry, return_value); - } - } - - void boss_init() - { - if (g_boss.is_initialized) - return; - - // start the boss thread - std::thread t(boss_thread); - t.detach(); - - g_boss.is_initialized = true; - } -} diff --git a/src/Cafe/IOSU/legacy/iosu_boss.h b/src/Cafe/IOSU/legacy/iosu_boss.h deleted file mode 100644 index 88fa94f7..00000000 --- a/src/Cafe/IOSU/legacy/iosu_boss.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "iosu_ioctl.h" - -// custom dev/boss protocol (Cemu only) -#define IOSU_BOSS_REQUEST_CEMU (0xEE) - -typedef struct -{ - uint32 requestCode; - // input - uint32 accountId; - char* taskId; - bool bool_parameter; - uint64 titleId; - uint32 timeout; - uint32 waitState; - uint32 uk1; - void* settings; - - // output - uint32 returnCode; // ACP return value - union - { - struct - { - uint32 exec_count; - uint32 result; - } u32; - struct - { - uint32 exec_count; - uint64 result; - } u64; - }; -}iosuBossCemuRequest_t; - -#define IOSU_NN_BOSS_TASK_RUN (0x01) -#define IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH (0x02) -#define IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH (0x03) -#define IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE (0x04) -#define IOSU_NN_BOSS_TASK_GET_TURN_STATE (0x05) -#define IOSU_NN_BOSS_TASK_WAIT (0x06) -#define IOSU_NN_BOSS_TASK_REGISTER (0x07) -#define IOSU_NN_BOSS_TASK_IS_REGISTERED (0x08) -#define IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN (0x09) -#define IOSU_NN_BOSS_TASK_UNREGISTER (0x0A) -#define IOSU_NN_BOSS_TASK_START_SCHEDULING (0x0B) -#define IOSU_NN_BOSS_TASK_STOP_SCHEDULING (0x0C) - -namespace iosu -{ - void boss_init(); -} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/boss/boss_common.cpp b/src/Cafe/IOSU/nn/boss/boss_common.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/Cafe/IOSU/nn/boss/boss_common.h b/src/Cafe/IOSU/nn/boss/boss_common.h new file mode 100644 index 00000000..fcc66ebb --- /dev/null +++ b/src/Cafe/IOSU/nn/boss/boss_common.h @@ -0,0 +1,431 @@ +#pragma once +#include "Common/CafeString.h" + +namespace nn::boss +{ + typedef uint32 BossResult; + + struct VTableEntry + { + uint16be offsetA{0}; + uint16be offsetB{0}; + MEMPTR ptr; + }; + static_assert(sizeof(VTableEntry) == 8); + + struct TitleId + { + uint64be u64{}; + + static bool IsValid(TitleId* _thisptr); + static TitleId* ctorDefault(TitleId* _thisptr); + static TitleId* ctorFromTitleId(TitleId* _thisptr, uint64 titleId); // __ct__Q3_2nn4boss7TitleIDFUL + static TitleId* ctorCopy(TitleId* _thisptr, TitleId* titleId); // __ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID + static bool operator_ne(TitleId* _thisptr, TitleId* titleId); + + }; + static_assert(sizeof(TitleId) == 8); + + struct TaskId + { + CafeString<8> id; + + TaskId() = default; + TaskId(const char* taskId) { id.assign(taskId); } + + static TaskId* ctorDefault(TaskId* _thisptr); + static TaskId* ctorFromString(TaskId* _thisptr, const char* taskId); + + //auto operator<=>(const TaskId& other) const = default; + std::strong_ordering operator<=>(const TaskId& other) const noexcept { + return id <=> other.id; // Delegate to CafeString's operator<=> + } + }; + static_assert(sizeof(TaskId) == 8); + + struct Title + { + uint32be accountId{}; // 0x00 + TitleId titleId{}; // 0x8 + MEMPTR vTablePtr{}; // 0x10 + + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator s_titleVTable; + + static Title* ctor(Title* _this); + static void dtor(Title* _this, uint32 options); + static void InitVTable(); + }; + static_assert(sizeof(Title) == 0x18); + + struct DirectoryName + { + CafeString<8> name2; + + static DirectoryName* ctor(DirectoryName* _thisptr); + static const char* operator_const_char(DirectoryName* _thisptr); + }; + static_assert(sizeof(DirectoryName) == 8); + + struct BossAccount // the actual class name is "Account" and while the boss namespace helps us separate this from Account(.h) we use an alternative name to avoid confusion + { + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator s_VTable; + + uint32be accountId; + MEMPTR vTablePtr; + + static BossAccount* ctor(BossAccount* _this, uint32 accountId); + static void dtor(BossAccount* _this, uint32 options); + static void InitVTable(); + + }; + static_assert(sizeof(BossAccount) == 8); + + enum class TaskWaitState : uint32 // for Task::Wait() + { + Done = 1, + }; + + enum class TaskTurnState : uint32 + { + Ukn = 0, + Stopped = 1, + Ready = 4, + Running = 6, + Done = 7, // how does this differ from DoneSuccess? + DoneSuccess = 16, + DoneError = 17 + }; + + enum class TaskState : uint8 + { + Initial = 0, + Stopped = 1, + Ready = 4, // waiting for turn to run + Running = 6, + Done = 7, + }; + + enum class TaskType : uint16 + { + NbdlTaskSetting = 2, + RawDlTaskSetting_1 = 1, + RawDlTaskSetting_3 = 3, + RawDlTaskSetting_9 = 9, + RawUlTaskSetting = 4, + PlayLogUploadTaskSetting = 5, + PlayReportSetting = 6, + DataStoreDownloadSetting = 8, + NbdlDataListTaskSetting = 10 + }; + + struct TaskSettingCore // the setting struct as used by IOSU + { + struct LifeTime + { + uint32be high; + uint32be low; + }; + + uint32be persistentId; + uint32be ukn04; + uint32be ukn08; + uint32be ukn0C; + uint32be ukn10; + uint32be ukn14; + uint32be ukn18; + uint32be ukn1C; + TaskId taskId; // +0x20 + betype taskType; // +0x28 + uint8be priority; // +0x2A + uint8be mode; // +0x2B + uint8be permission; // +0x2C + uint16be intervalA; // +0x2E + uint32be intervalB; // +0x30 + uint32be unk34; // +0x34 - could be padding + LifeTime lifeTime; // +0x38 - this is a 64bit value, but the whole struct has a size of 0x1004 and doesnt preserve the alignment in an array. Its probably handled as two 32bit values? + uint8be httpProtocol; // +0x40 + uint8be internalClientCert; // +0x41 + uint16be httpOption; // +0x42 + uint8 ukn44[0x48 - 0x44]; // padding? + CafeString<256> url; // +0x48 + CafeString<64> lastModifiedTime; // +0x148 + uint8be internalCaCert[3]; // +0x188 + uint8 ukn18B; // +0x18B - padding? + uint32be httpTimeout; // +0x18C + CafeString<128> caCert[3]; // +0x190 - 3 entries, each 0x80 bytes + CafeString<128> clientCertName; // +0x310 + CafeString<128> clientCertKey; // +0x390 + struct HttpHeader + { + CafeString<32> name; // +0x00 + CafeString<64> value; // +0x20 + }; + HttpHeader httpHeaders[3]; // +0x410 + CafeString<96> httpQueryString; // +0x530 + CafeString<512> serviceToken; // +0x590 + + uint8 ukn790[0x7C0 - 0x790]; + // after 0x7C0 the task-specific fields seem to start? + + union + { + struct + { + uint32be optionValue; // +0x7C0 + CafeString<32> largeHttpHeaderKey; // +0x7C4 + CafeString<512> largeHttpHeaderValue; // +0x7E4 + }rawUl; // RawUlTaskSetting + struct + { + uint32be optionValue; // +0x7C0 + CafeString<32> largeHttpHeaderKey; // +0x7C4 + CafeString<512> largeHttpHeaderValue; // +0x7E4 + // inherits the above values from RawUlTaskSetting + // the play report fields are too large to fit into the available space, so instead they are passed via their own system call that attaches it to the task. See PlayReportSetting::RegisterPreprocess + }playReportSetting; + struct + { + uint8be newArrival; // +0x7C0 + uint8be led; // +0x7C1 + uint8be ukn7C2[6]; // +0x7C2 - padding? + CafeString<8> bossDirectory; // +0x7C8 + CafeString<32> fileName; // +0x7D0 Not 100% sure this is fileName + }rawDl; // RawDlTaskSetting + struct + { + CafeString<32> bossCode; // +0x7C0 + CafeString<8> bossDirectory; // +0x7E0 + uint32be ukn7E8; + uint32be ukn7EC; + uint32be directorySizeLimitHigh; // +0x7F0 + uint32be directorySizeLimitLow; // +0x7F4 + CafeString<32> fileName; // +0x7F8 + // more fields here... + }nbdl; + struct + { + uint8 finalPadding[0xC00 - 0x7C0]; + }paddedBlock; + }; + }; + static_assert(sizeof(TaskSettingCore) == 0xC00); + + struct TaskSetting : public TaskSettingCore + { + uint8 paddingC00[0x1000 - 0xC00]; + MEMPTR vTablePtr; // +0x1000 + + struct VTableTaskSetting + { + VTableEntry rtti; + VTableEntry dtor; + VTableEntry RegisterPreprocess; // todo - double check the offset + VTableEntry unk1; + }; + static inline SysAllocator s_VTable; + + static TaskSetting* ctor(TaskSetting* _thisptr); + static void dtor(TaskSetting* _this, uint32 options); + static bool IsPrivileged(TaskSetting* _thisptr); + static void InitializeSetting(TaskSetting* _thisptr); + static void InitVTable(); + }; + static_assert(offsetof(TaskSetting, priority) == 0x2A); + static_assert(offsetof(TaskSetting, mode) == 0x2B); + static_assert(offsetof(TaskSetting, permission) == 0x2C); + static_assert(offsetof(TaskSetting, intervalA) == 0x2E); + static_assert(offsetof(TaskSetting, intervalB) == 0x30); + static_assert(offsetof(TaskSetting, lifeTime) == 0x38); + static_assert(offsetof(TaskSetting, httpProtocol) == 0x40); + static_assert(offsetof(TaskSetting, internalClientCert) == 0x41); + static_assert(offsetof(TaskSetting, httpOption) == 0x42); + static_assert(offsetof(TaskSetting, url) == 0x48); + static_assert(offsetof(TaskSetting, lastModifiedTime) == 0x148); + static_assert(offsetof(TaskSetting, internalCaCert) == 0x188); + static_assert(offsetof(TaskSetting, httpTimeout) == 0x18C); + static_assert(offsetof(TaskSetting, caCert) == 0x190); + static_assert(offsetof(TaskSetting, clientCertName) == 0x310); + static_assert(offsetof(TaskSetting, clientCertKey) == 0x390); + static_assert(offsetof(TaskSetting, httpHeaders) == 0x410); + static_assert(offsetof(TaskSetting, httpQueryString) == 0x530); + static_assert(offsetof(TaskSetting, serviceToken) == 0x590); + // rawUl + static_assert(offsetof(TaskSetting, rawUl.optionValue) == 0x7C0); + static_assert(offsetof(TaskSetting, rawUl.largeHttpHeaderKey) == 0x7C4); + static_assert(offsetof(TaskSetting, rawUl.largeHttpHeaderValue) == 0x7E4); + // rawDl + static_assert(offsetof(TaskSetting, rawDl.newArrival) == 0x7C0); + static_assert(offsetof(TaskSetting, rawDl.bossDirectory) == 0x7C8); + static_assert(offsetof(TaskSetting, rawDl.fileName) == 0x7D0); + // nbdl + static_assert(offsetof(TaskSetting, nbdl.bossCode) == 0x7C0); + static_assert(offsetof(TaskSetting, nbdl.bossDirectory) == 0x7E0); + + static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000); + static_assert(sizeof(TaskSetting) == 0x1004); + + /* NetTaskSetting */ + + struct NetTaskSetting : TaskSetting + { + struct VTableNetTaskSetting : public VTableTaskSetting + { }; + static inline SysAllocator s_VTable; + + static NetTaskSetting* ctor(NetTaskSetting* _thisptr); + static BossResult AddCaCert(NetTaskSetting* _thisptr, const char* name); + static BossResult SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken); + static BossResult AddInternalCaCert(NetTaskSetting* _thisptr, char certId); + static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId); + static void InitVTable(); + }; + static_assert(sizeof(NetTaskSetting) == 0x1004); + + /* NbdlTaskSetting */ + + struct NbdlTaskSetting : NetTaskSetting + { + struct VTableNbdlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableNbdlTaskSetting) == 8*5); + static inline SysAllocator s_VTable; + + static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr); + static BossResult Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* bossDirectory); + static BossResult SetFileName(NbdlTaskSetting* _thisptr, const char* fileName); + static void InitVTable(); + }; + static_assert(sizeof(NbdlTaskSetting) == 0x1004); + + /* RawUlTaskSetting */ + + struct RawUlTaskSetting : NetTaskSetting + { + uint32be ukRaw1; // 0x1004 + uint32be ukRaw2; // 0x1008 + uint32be ukRaw3; // 0x100C + uint8 rawSpace[0x200]; // 0x1010 + + struct VTableRawUlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableRawUlTaskSetting) == 8*5); + static inline SysAllocator s_VTable; + + static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr); + static void dtor(RawUlTaskSetting* _this, uint32 options); + static void InitVTable(); + }; + static_assert(sizeof(RawUlTaskSetting) == 0x1210); + + /* RawDlTaskSetting */ + struct RawDlTaskSetting : NetTaskSetting + { + struct VTableRawDlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableRawDlTaskSetting) == 8*5); + static inline SysAllocator s_VTable; + + static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr); + static BossResult Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* bossDirectory); + + static void InitVTable(); + }; + static_assert(sizeof(RawDlTaskSetting) == 0x1004); + + /* PlayReportSetting */ + + struct PlayReportSetting : RawUlTaskSetting + { + MEMPTR ukn1210_ptr; // 0x1210 + uint32be ukn1214_size; // 0x1214 + uint32be ukPlay3; // 0x1218 + uint32be ukPlay4; // 0x121C + + struct VTablePlayReportSetting : public VTableRawUlTaskSetting + {}; + static_assert(sizeof(VTablePlayReportSetting) == 8*5); + static inline SysAllocator s_VTable; + + static PlayReportSetting* ctor(PlayReportSetting* _this); + static void dtor(PlayReportSetting* _this, uint32 options); + static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size); + static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value); + static void InitVTable(); + }; + static_assert(sizeof(PlayReportSetting) == 0x1220); + + /* Storage */ + enum class StorageKind : uint32 + { + StorageNbdl = 0, + StorageRawDl = 1, + }; + + struct DataName + { + CafeString<32> name; + + static DataName* ctor(DataName* _this); // __ct__Q3_2nn4boss8DataNameFv + static const char* operator_const_char(DataName* _this); // __opPCc__Q3_2nn4boss8DataNameCFv + + const char* c_str() const + { + return name.c_str(); + } + }; + static_assert(sizeof(DataName) == 32); + + /* IPC commands for /dev/boss */ + enum class BossCommandId : uint32 + { + // task operations + TaskIsRegistered = 0x69, + TaskRegisterA = 0x6A, + TaskRegisterForImmediateRunA = 0x6B, + TaskUnregister = 0x6D, + TaskRun = 0x78, + TaskStartScheduling = 0x77, + TaskStopScheduling = 0x79, + TaskWaitA = 0x7A, + TaskGetHttpStatusCodeA = 0x7C, + TaskGetTurnState = 0x7E, + TaskGetContentLength = 0x82, + TaskGetProcessedLength = 0x83, + // storage operations + StorageExist = 0x87, + StorageGetDataList = 0xB0, + // NsData operations + NsDataExist = 0x90, + NsDataRead = 0x93, + NsDataGetSize = 0x96, + NsDataDeleteFile = 0xA7, + NsDataDeleteFileWithHistory = 0xA8, + NsFinalize = 0xB3, + // Title operations + TitleSetNewArrivalFlag = 0x9E, + + // most (if not all?) opcodes seem to have a secondary form with an additional titleId parameter in the high range around 0x150 and serviceId 1 + + // unknown commands + UknA7 = 0xA5, + DeleteDataRelated = 0xA6, + }; + + +} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/boss/boss_service.cpp b/src/Cafe/IOSU/nn/boss/boss_service.cpp new file mode 100644 index 00000000..cc508433 --- /dev/null +++ b/src/Cafe/IOSU/nn/boss/boss_service.cpp @@ -0,0 +1,1431 @@ +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "util/helpers/helpers.h" +#include "Cafe/Filesystem/fsc.h" + +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/IOSU/nn/iosu_nn_service.h" + +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/CafeSystem.h" +#include "config/ActiveSettings.h" + +#include "boss_service.h" +#include "boss_common.h" + +#include +#include +#include +#include +#include + +#include "Cemu/ncrypto/ncrypto.h" +#include "Cemu/napi/napi_helper.h" +#include "IOSU/legacy/iosu_crypto.h" +#include "util/crypto/aes128.h" + +namespace iosu::boss +{ + using namespace ::nn::boss; + + static constexpr nnResult RESULT_SUCCESS = 0x200080; + static constexpr nnResult RESULT_C0203880 = 0xC0203880; + static constexpr nnResult RESULT_NOTEXIST = 0xA021F900; + + static constexpr nnResult RESULT_STORAGE_NOTEXIST = 0xA025AA00; + static constexpr nnResult RESULT_FADENTRY_NOTEXIST = 0xA021FB00; + + template + void AppendHeaderParam(CurlRequestHelper& request, const char* fieldName, const char* format, TArgs&& ... args) + { + request.addHeaderField(fieldName, fmt::format(fmt::runtime(format), std::forward(args)...).c_str()); + } + + class StorageDatabase // FAD + { + static constexpr uint32 FAD_ENTRY_MAX_COUNT = 512; // max entries in a single FAD file + + public: + struct BossStorageFadEntry + { + CafeString<32> fileName; // 0x00 + uint32be fileId; // 0x20 + uint32 ukn24; // 0x24 + uint32 flags; // 0x28 + uint32 memo_2C; // 0x2C + uint64be entryCreationTimestamp; + // flags: + // 0x80000000 ReadFlag + // 0x40000000 DeleteProtectionFlag + // 0x20000000 New arrival flag? + }; + static_assert(sizeof(BossStorageFadEntry) == 0x38); + + struct BossStorageFadFile + { + uint8 _00[0x08]; + BossStorageFadEntry entries[FAD_ENTRY_MAX_COUNT]; + }; + static_assert(sizeof(BossStorageFadFile) == 28680); + + static bool CheckIfStorageExists(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId) + { + fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, bossDirectory.name2.c_str()); + std::error_code ec; + return fs::exists(fadPath, ec); + } + + nnResult Load(TaskId& taskId, uint64 titleId, uint32 persistentId) + { + // todo - if same FAD db already loaded then skip this + m_hasValidFadData = false; + memset(&m_fadData, 0, sizeof(m_fadData)); + fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, taskId.id.c_str()); + std::error_code ec; + if (!fs::exists(fadPath, ec)) + fs::create_directories(fadPath, ec); + fadPath /= "fad.db"; + m_fadFilePath = fadPath; + FileStream* fs = FileStream::openFile2(fadPath); + if (!fs) + return RESULT_STORAGE_NOTEXIST; + fs->readData(&m_fadData, sizeof(m_fadData)); + delete fs; + m_hasValidFadData = true; // the file may not exist yet, so consider it valid even if we cant open it + return RESULT_SUCCESS; + } + + nnResult CreateNewStorage(TaskId& taskId, uint64 titleId, uint32 persistentId) + { + memset(&m_fadData, 0, sizeof(m_fadData)); + fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, taskId.id.c_str()); + std::error_code ec; + if (!fs::exists(fadPath, ec)) + fs::create_directories(fadPath, ec); + m_fadFilePath = fadPath / "fad.db"; + FileStream* fs = FileStream::createFile2(m_fadFilePath); + if (fs) + { + fs->writeData(&m_fadData, sizeof(m_fadData)); + delete fs; + m_hasValidFadData = true; + } + else + { + cemuLog_log(LogType::Force, "Failed to create FAD data file at {}", _pathToUtf8(m_fadFilePath)); + return NN_RESULT_PLACEHOLDER_ERROR; + } + return RESULT_SUCCESS; + } + + void Store() + { + if (!m_hasValidFadData) + return; + FileStream* fs = FileStream::createFile2(m_fadFilePath); + if (fs) + { + fs->writeData(&m_fadData, sizeof(m_fadData)); + delete fs; + } + else + { + cemuLog_log(LogType::Force, "Failed to store FAD data to {}", m_fadFilePath.string()); + } + } + + void Clear() + { + m_hasValidFadData = false; + memset(&m_fadData, 0, sizeof(m_fadData)); + } + + void DeleteEntryByFileName(CafeString<32> fileName) + { + cemu_assert_debug(m_hasValidFadData); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (m_fadData.entries[i].fileName == fileName) + { + // shift remaining entries + memmove(m_fadData.entries + i, m_fadData.entries + i + 1, (FAD_ENTRY_MAX_COUNT - i - 1) * sizeof(BossStorageFadEntry)); + // reset last entry + memset(m_fadData.entries + FAD_ENTRY_MAX_COUNT - 1, 0, sizeof(BossStorageFadEntry)); + return; + } + } + } + + nnResult AddEntry(CafeString<32> fileName, uint32 fileId) + { + if (!fileId) + { + cemuLog_log(LogType::Force, "Cannot add BOSS file with fileId 0"); + return false; + } + if (fileName.empty()) + return false; + DeleteEntryByFileName(fileName); + cemu_assert_debug(m_hasValidFadData); + for (sint32 i=0; i& dataList) + { + cemu_assert_debug(m_hasValidFadData); + dataList.clear(); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (m_fadData.entries[i].fileId == 0) + break; // end of list + DataName dataName; + dataName.name = m_fadData.entries[i].fileName; + dataList.push_back(dataName); + } + } + + bool GetEntryByFilename(const DataName& fileName, BossStorageFadEntry& outEntry) + { + if (fileName.name.empty()) + return false; + cemu_assert_debug(m_hasValidFadData); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (m_fadData.entries[i].fileName == fileName.name) + { + outEntry = m_fadData.entries[i]; + return true; + } + if (m_fadData.entries[i].fileId == 0) + break; // end of list + } + return false; + } + + private: + BossStorageFadFile m_fadData; + bool m_hasValidFadData = false; + fs::path m_fadFilePath; + }; + StorageDatabase m_fadDb; + + class NsDataAccessor + { + public: + nnResult Open(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId, DataName& fileName) + { + m_isValid = false; + m_nsDataPath.clear(); + TaskId taskId(bossDirectory.name2.c_str()); + nnResult r = m_fadDb.Load(taskId, titleId, persistentId); + if (NN_RESULT_IS_FAILURE(r)) + return r; + if (!m_fadDb.GetEntryByFilename(fileName, m_fadEntry)) + return RESULT_FADENTRY_NOTEXIST; + m_nsDataPath = BuildNsDataPath(bossDirectory, titleId, persistentId, m_fadEntry.fileId); + m_isValid = true; + m_bossDirectory = bossDirectory; + m_titleId = titleId; + m_persistentId = persistentId; + m_fileName = fileName; + return RESULT_SUCCESS; + } + + void Close() + { + m_isValid = false; + } + + nnResult Delete() + { + cemu_assert_debug(m_isValid); // only call this after successful Open() + std::error_code ec; + if (!fs::remove(m_nsDataPath, ec) ) + cemuLog_log(LogType::Force, "Failed to delete BOSS file {}", _pathToUtf8(m_nsDataPath)); + // remove from FAD + TaskId taskId(m_bossDirectory.name2.c_str()); + nnResult r = m_fadDb.Load(taskId, m_titleId, m_persistentId); + if (NN_RESULT_IS_SUCCESS(r)) + m_fadDb.DeleteEntryByFileName(m_fileName.name); + m_isValid = false; + return RESULT_SUCCESS; + } + + bool Exists() const + { + if (!m_isValid) + return false; + // check if the file actually exists on the host filesystem + std::error_code ec; + if (m_nsDataPath.empty()) + return false; + bool r = fs::exists(m_nsDataPath, ec); + if (!r) + cemuLog_log(LogType::Force, "BOSS: File exists in FAD cache but not on disk {}. To fix this reset the SpotPass cache. In the menu under debug select \"Clear SpotPass cache\"", _pathToUtf8(m_nsDataPath)); + return r; + } + + nnResult GetSize(uint32& fileSize) const + { + cemu_assert_debug(m_isValid); // only call this after successful Open() + if (!m_isValid) + return NN_RESULT_PLACEHOLDER_ERROR; + std::unique_ptr fs(FileStream::openFile2(m_nsDataPath)); + if (!fs) + return NN_RESULT_PLACEHOLDER_ERROR; + fileSize = (uint32)fs->GetSize(); + return RESULT_SUCCESS; + } + + bool Read(void* buffer, uint32 size, uint32 offset, uint32& bytesRead) + { + if (!m_isValid) + return false; + std::unique_ptr fs(FileStream::openFile2(m_nsDataPath)); + if (!fs) + return false; + fs->SetPosition(offset); + bytesRead = (uint32)fs->readData(buffer, size); + return true; + } + + private: + fs::path BuildNsDataPath(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId, uint32 dataId) + { + return ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), bossDirectory.name2.c_str(), dataId); + } + + bool m_isValid{false}; + StorageDatabase::BossStorageFadEntry m_fadEntry; + fs::path m_nsDataPath; + // if m_isValid is true, these hold the current file information: + DirectoryName m_bossDirectory; + uint64 m_titleId{}; + uint32 m_persistentId{}; + DataName m_fileName; + }; + + NsDataAccessor m_nsDataAccessor; + + class RegisteredTask + { + public: + RegisteredTask(const TaskId& taskId, const TaskSettingCore& taskSettings) + : m_taskId(taskId), m_taskSettings(taskSettings) + { + m_persistentId = iosuAct_getAccountIdOfCurrentAccount(); + } + + nnResult Run() + { + std::unique_lock _l(m_mutex); + cemu_assert_debug(m_taskState == TaskState::Initial || m_taskState == TaskState::Done); + m_taskTurnState = TaskTurnState::Ready; + m_taskState = TaskState::Ready; + return RESULT_SUCCESS; + } + + TaskState GetState() + { + std::unique_lock _l(m_mutex); + return m_taskState; + } + + TaskTurnState GetTurnState() + { + std::unique_lock _l(m_mutex); + return m_taskTurnState; + } + + sint32 GetHttpStatusCode() const + { + return m_httpStatusCode; + } + + uint32 GetContentLength() const + { + return m_contentLength; + } + + uint32 GetProcessedLength() const + { + return m_contentLength; // todo - unlike content length, this value is getting updated as the download happens. But for now we just return the content length + } + + nnResult TaskDoRequest(CURL* curl) + { + std::unique_lock _l(m_mutex); + if (m_taskState != TaskState::Ready) + { + cemuLog_log(LogType::Force, "Task {} is not ready to run, current state: {}", m_taskId.id.c_str(), static_cast(m_taskState)); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + m_taskState = TaskState::Running; + m_taskTurnState = TaskTurnState::Running; + m_contentLength = 0; + _l.unlock(); + if (!ActiveSettings::IsOnlineEnabled()) + { + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + uint64 titleId = CafeSystem::GetForegroundTitleId(); + // construct request URL + std::string requestUrl; + if (!m_taskSettings.url.empty()) + requestUrl.assign(m_taskSettings.url.c_str()); // custom url + else + { + switch (ActiveSettings::GetNetworkService()) + { + case NetworkService::Pretendo: + requestUrl = PretendoURLs::BOSSURL; + break; + case NetworkService::Custom: + requestUrl = GetNetworkConfig().urls.BOSS.GetValue(); + break; + case NetworkService::Nintendo: + default: + requestUrl = NintendoURLs::BOSSURL; + break; + } + } + char languageCode[8]; + switch (GetConfig().console_language) + { + case CafeConsoleLanguage::JA: + strcpy(languageCode, "ja"); + break; + case CafeConsoleLanguage::EN: + strcpy(languageCode, "en"); + break; + case CafeConsoleLanguage::FR: + strcpy(languageCode, "fr"); + break; + case CafeConsoleLanguage::DE: + strcpy(languageCode, "de"); + break; + case CafeConsoleLanguage::IT: + strcpy(languageCode, "it"); + break; + case CafeConsoleLanguage::ES: + strcpy(languageCode, "es"); + break; + case CafeConsoleLanguage::ZH: + strcpy(languageCode, "zh"); + break; + case CafeConsoleLanguage::KO: + strcpy(languageCode, "ko"); + break; + case CafeConsoleLanguage::NL: + strcpy(languageCode, "nl"); + break; + case CafeConsoleLanguage::PT: + strcpy(languageCode, "pt"); + break; + case CafeConsoleLanguage::RU: + strcpy(languageCode, "ru"); + break; + case CafeConsoleLanguage::TW: + strcpy(languageCode, "tw"); // usually zh-tw? + break; + default: + strcpy(languageCode, "en"); + break; + } + const char* countryCode = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); + if (m_taskSettings.taskType == TaskType::NbdlDataListTaskSetting) + { + // use bossCode (and fileName?) as part of the URL + requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), "1", m_taskSettings.nbdl.bossCode.c_str(), m_taskId.id.c_str(), countryCode, languageCode)); + cemu_assert_unimplemented(); + cemuLog_logDebug(LogType::Force, "IOSU-BOSS: Unsupported task type: {}", static_cast(m_taskSettings.taskType.value())); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + m_contentLength = 0; + m_httpStatusCode = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + else if (m_taskSettings.taskType == TaskType::NbdlTaskSetting) + { + // todo - language and country code come from the task settings + cemu_assert_debug(m_taskSettings.nbdl.fileName.empty()); // todo - changes the url + const char* apiVersion = "1"; + requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), apiVersion, m_taskSettings.nbdl.bossCode.c_str(), m_taskId.id.c_str(), countryCode, languageCode)); + } + else if (m_taskSettings.taskType == TaskType::PlayReportSetting) + { + cemuLog_logDebug(LogType::Force, "IOSU-BOSS: PlayReport tasks are not implemented yet"); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + m_contentLength = 0; + m_httpStatusCode = 200; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + else + { + cemuLog_logDebug(LogType::Force, "IOSU-BOSS: Unknown task type: {}", static_cast(m_taskSettings.taskType.value())); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + m_contentLength = 0; + m_httpStatusCode = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + CurlRequestHelper request; + request.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::CUSTOM); + // add certs + SetupSSL(request); + // parameters + AppendHeaderParam(request, "X-BOSS-Digest:", ""); // todo - implement this + AppendHeaderParam(request, "X-Boss-UniqueId", "{:05x}", ((titleId >> 8) & 0xFFFFF)); + AppendHeaderParam(request, "X-BOSS-TitleId", "{:016x}", titleId); + if (!m_taskSettings.serviceToken.empty()) + AppendHeaderParam(request, "X-Nintendo-ServiceToken", "{}", m_taskSettings.serviceToken.c_str()); + + if (!request.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed BOSS request")); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + sint32 httpStatusCode = request.GetHTTPStatusCode(); + m_httpStatusCode = httpStatusCode; + m_contentLength = request.getReceivedData().size(); // note - for Nbdl tasks which length is returned? + + { + if (httpStatusCode != 200) + { + cemuLog_logDebug(LogType::Force, "BOSS task_run: Received unexpected HTTP response code {}", httpStatusCode); + cemuLog_logDebug(LogType::Force, "URL: {}", requestUrl); + } + if (httpStatusCode == 404) + { + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + cemuLog_logDebug(LogType::Force, "BOSS task_run: Received 404 Not Found for URL: {}", requestUrl); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + } + + // for Nbdl tasks get the list of files to download + if (m_taskSettings.taskType == TaskType::NbdlTaskSetting) + { + std::vector queuedFiles = ParseNbdlTasksheet(request.getReceivedData()); + cemuLog_log(LogType::Force, "SpotPass - NbdlTask has {} files to download:", queuedFiles.size()); + for (const auto& file : queuedFiles) + { + DownloadNbdlFile(file); + } + } + + if (m_taskSettings.taskType == TaskType::RawDlTaskSetting_1 || m_taskSettings.taskType == TaskType::RawDlTaskSetting_3 || m_taskSettings.taskType == TaskType::RawDlTaskSetting_9) + { + cemu_assert_unimplemented(); + } + { + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneSuccess; + } + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + std::recursive_mutex& GetMutex() { return m_mutex; } + private: + struct NbdlQueuedFile + { + NbdlQueuedFile(std::string_view url, std::string_view fileName, uint32 dataId, uint32 fileType, uint32 size) + : url(url), fileName(fileName), dataId(dataId), fileType(fileType), size(size) + {} + + std::string url; + std::string fileName; + uint32 dataId; + uint32 fileType; // 0 = AppData, 1 = Message, 2 = Unknown + uint32 size; + }; + + std::vector ParseNbdlTasksheet(std::span xmlContent) + { + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_buffer(xmlContent.data(), xmlContent.size()); + if (!result) + return {}; + std::vector fileList; + for (pugi::xml_node sheet = doc.child("TaskSheet"); sheet; sheet = sheet.next_sibling("TaskSheet")) + { + pugi::xml_node serviceStatus = sheet.child("ServiceStatus"); + if (strcmp(serviceStatus.child_value(), "close") == 0) + { + // service is closed, skip all files + cemuLog_log(LogType::Force, "BossNbdl: Service is closed, skipping all files in tasksheet"); + continue; + } + else if (strcmp(serviceStatus.child_value(), "open") != 0) + { + cemuLog_log(LogType::Force, "BossNbdl: Unknown ServiceStatus value: {}", serviceStatus.child_value()); + } + // read and verify titleId + pugi::xml_node nodeTitleId = sheet.child("TitleId"); + if (nodeTitleId) + { + uint64 titleId; + TitleIdParser::ParseFromStr(nodeTitleId.child_value(), titleId); + if (titleId != CafeSystem::GetForegroundTitleId()) + cemuLog_log(LogType::Force, "BossNbdl: TitleId in tasksheet ({:016x}) does not match foreground titleId ({:016x})", titleId, CafeSystem::GetForegroundTitleId()); + } + else + { + cemuLog_log(LogType::Force, "BossNbdl: Missing TitleId field in tasksheet"); + } + // read and verify taskId + pugi::xml_node nodeTaskId = sheet.child("TaskId"); + if (nodeTaskId) + { + if (strcmp(nodeTaskId.child_value(), m_taskId.id.c_str()) != 0) + cemuLog_log(LogType::Force, "BossNbdl: TaskId in tasksheet ({}) does not match current taskId ({})", nodeTaskId.child_value(), m_taskId.id.c_str()); + } + else + { + cemuLog_log(LogType::Force, "BossNbdl: Missing TaskId field in tasksheet"); + } + + // read files list + pugi::xml_node files = sheet.child("Files"); + if (!files) + continue; + for (pugi::xml_node file = files.child("File"); file; file = file.next_sibling("File")) + { + pugi::xml_node fileName = file.child("Filename"); + if (!fileName) + continue; + pugi::xml_node dataId = file.child("DataId"); + if (!dataId) + continue; + pugi::xml_node url = file.child("Url"); + if (!url) + continue; + pugi::xml_node size = file.child("Size"); + if (!size) + continue; + pugi::xml_node type = file.child("Type"); + if (!type) + continue; + uint32 fileType; + if (strcmp(type.child_value(), "AppData") == 0) + fileType = 0; + else if (strcmp(type.child_value(), "Message") == 0) + fileType = 1; + else + { + cemuLog_log(LogType::Force, "BossNbdl: Unknown file Type value: {}", type.child_value()); + fileType = 0; + } + + if (!file.child("AutoDelete").empty()) + cemuLog_log(LogType::Force, "BossNbdl: Field AutoDelete is set but not supported. Value: {}", file.child_value("AutoDelete")); + + // fields and categories which we dont handle yet: + // Attributes, Attribute1, Attribute2, Attribute3 + // Notify, Conditions, SecInterval, LED, Played + + fileList.emplace_back( + url.child_value(), + fileName.child_value(), + dataId.text().as_int(), + fileType, + size.text().as_int() + ); + } + } + return fileList; + } + + void SetupSSL(CurlRequestHelper& request) + { + request.ClearCaCertIds(); + request.ClearClientCertIds(); + for (sint32 i=0; i<3; i++) + { + if (m_taskSettings.internalCaCert[i] != 0) + request.AddCaCertId(m_taskSettings.internalCaCert[i]); + } + request.AddCaCertId(105); + if (m_taskSettings.internalClientCert != 0) + request.AddCaCertId(m_taskSettings.internalClientCert); + else + request.AddCaCertId(3); // whats the default for each task type? + cemu_assert_debug(m_taskSettings.caCert[0].empty()); + cemu_assert_debug(m_taskSettings.caCert[1].empty()); + cemu_assert_debug(m_taskSettings.caCert[2].empty()); + cemu_assert_debug(m_taskSettings.clientCertName.empty()); + } + + void DownloadNbdlFile(const NbdlQueuedFile& nbdlFile) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); // todo - use titleId from task settings? + fs::path dataFilePath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), m_taskId.id.c_str(), nbdlFile.dataId); + + // if the file already exists locally, skip download + // but still add the entry to the FAD db + std::error_code ec; + if (fs::exists(dataFilePath)) + { + cemuLog_log(LogType::Force, "\t- {} (DataId:{:08x} {}KB) (Skipping, already downloaded)", nbdlFile.fileName, nbdlFile.dataId, (nbdlFile.size+1023)/1024); + TrackDownloadedNbdlFile(nbdlFile); + return; + } + cemuLog_log(LogType::Force, "\t- {} (DataId:{:08x} {}KB)", nbdlFile.fileName, nbdlFile.dataId, (nbdlFile.size+1023)/1024); + + CurlRequestHelper request; + request.initate(ActiveSettings::GetNetworkService(), nbdlFile.url, CurlRequestHelper::SERVER_SSL_CONTEXT::CUSTOM); + SetupSSL(request); + + bool r = request.submitRequest(); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to download SpotPass nbdl file: {}", nbdlFile.fileName); + return; + } + + // decrypt and store file + std::vector receivedData = request.getReceivedData(); + struct BossNbdlHeader + { + /* +0x00 */ uint32be magic; + /* +0x04 */ uint32be version; // guessed + /* +0x08 */ uint16be ukn08; // must always be 1 + /* +0x0A */ uint16be ukn0A; // must always be 2 + /* +0x0C */ uint8 nonce[0xC]; + /* +0x18 */ uint32 padding18; // unused + /* +0x1C */ uint32 padding1C; // unused + /* +0x20 */ + struct + { + uint8 uknHashData[0x20]; + } encryptedHeader; + }; + if (receivedData.size() < sizeof(BossNbdlHeader)) + { + cemuLog_log(LogType::Force, "Received SpotPass nbdl file is too small: {} bytes", receivedData.size()); + return; + } + BossNbdlHeader nbdlHeader; + memcpy(&nbdlHeader, receivedData.data(), sizeof(BossNbdlHeader)); + if (nbdlHeader.magic != 'boss' || nbdlHeader.version != 0x20001 || nbdlHeader.ukn08 != 1 || nbdlHeader.ukn0A != 2) + { + cemuLog_log(LogType::Force, "Received SpotPass nbdl file has incorrect header"); + return; + } + // file must be padded to 16 byte alignment for AES decryption (padding is cut off after decryption) + size_t originalFileSize = receivedData.size(); + const uint32 paddedFileSize = (originalFileSize + 0xF) & (~0xF); + receivedData.resize(paddedFileSize); + // extra validation + if (originalFileSize != nbdlFile.size) + { + cemuLog_log(LogType::Force, "SpotPass nbdl file size mismatch, expected {} bytes but got {} bytes", nbdlFile.size, originalFileSize); + return; + } + if (nbdlFile.fileName.find("..") != std::string::npos) + { + cemuLog_log(LogType::Force, "SpotPass nbdl filename contains suspicious characters"); + return; + } + // prepare nonce for AES128-CTR + uint8 aesNonce[0x10]; + memset(aesNonce, 0, sizeof(aesNonce)); + memcpy(aesNonce, nbdlHeader.nonce, 0xC); + aesNonce[0xF] = 1; + // decrypt + uint8 bossAesKey[16] = { 0x39,0x70,0x57,0x35,0x58,0x70,0x34,0x58,0x37,0x41,0x7a,0x30,0x71,0x5a,0x70,0x74 }; + memset(aesNonce, 0, sizeof(aesNonce)); + memcpy(aesNonce, nbdlHeader.nonce, 0xC); + aesNonce[0xF] = 1; + AES128CTR_transform(receivedData.data() + offsetof(BossNbdlHeader, encryptedHeader), receivedData.size() - sizeof(BossNbdlHeader::encryptedHeader), bossAesKey, aesNonce); + // get HMAC from header + uint8 fileHMAC[32]; + memcpy(fileHMAC, &((BossNbdlHeader*)receivedData.data())->encryptedHeader.uknHashData, 32); + // write decrypted data to filesystem using a temporary path + fs::path tmpDataPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/.{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), m_taskId.id.c_str(), nbdlFile.dataId); + if (!fs::exists(tmpDataPath.parent_path())) + fs::create_directories(tmpDataPath.parent_path(), ec); + FileStream* fs = FileStream::createFile2(tmpDataPath); + if (fs) + { + fs->writeData(receivedData.data() + sizeof(BossNbdlHeader), originalFileSize - sizeof(BossNbdlHeader)); + delete fs; + } + else + { + cemuLog_log(LogType::Force, "Failed to create temporary SpotPass nbdl file: {}", _pathToUtf8(tmpDataPath)); + return; + } + // to make sure that the file actually stored correctly to disk we read it back and verify the hash + auto readbackFileData = FileStream::LoadIntoMemory(tmpDataPath); + uint8 hmacHash[32]; + unsigned int hmacHashLen = 32; + HMAC(EVP_sha256(), "uyr5I8pu4ycq2pOT3D53Bp0n7jK8eyjLO5U20ocUNdN5muwUcC4By881UXECeM08", 64, readbackFileData->data(), originalFileSize - sizeof(BossNbdlHeader), hmacHash, &hmacHashLen); + if (memcmp(hmacHash, fileHMAC, 32) != 0) + { + // failed to verify hash + cemuLog_log(LogType::Force, "Failed to verify hash for SpotPass nbdl file: {}", _pathToUtf8(tmpDataPath)); + fs::remove(tmpDataPath, ec); + return; + } + // rename temporary file to final path + fs::rename(tmpDataPath, dataFilePath, ec); + // update FAD entry + TrackDownloadedNbdlFile(nbdlFile); + } + + void TrackDownloadedNbdlFile(const NbdlQueuedFile& nbdlFile) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult storageResult = m_fadDb.Load(m_taskId, titleId, m_persistentId); + if (storageResult == RESULT_STORAGE_NOTEXIST) + storageResult = m_fadDb.CreateNewStorage(m_taskId, titleId, m_persistentId); + if (NN_RESULT_IS_FAILURE(storageResult)) + { + cemuLog_log(LogType::Force, "Failed to create FAD database for task {}: {:08x}", m_taskId.id.c_str(), storageResult); + return; + } + CafeString<32> filenameStr; + filenameStr.assign(nbdlFile.fileName); + m_fadDb.AddEntry(filenameStr, nbdlFile.dataId); + m_fadDb.Store(); + // todo - DIDX and ref database + } + + TaskId m_taskId; + uint32 m_persistentId; + TaskSettingCore m_taskSettings; + std::recursive_mutex m_mutex; + TaskState m_taskState{ TaskState::Initial }; + TaskTurnState m_taskTurnState{ TaskTurnState::Ukn }; + sint32 m_httpStatusCode{ 0 }; + uint32 m_contentLength{ 0 }; // content length of the last request + }; + + class BossDaemon + { + public: + void Start() + { + if (m_threadRunning.exchange(true)) + return; + m_bossDaemonThread = std::thread(&BossDaemon::BossDaemonThread, this); + } + + void Stop() + { + if (!m_threadRunning.exchange(false)) + return; + m_bossDaemonThread.join(); + } + + void RegisterTask(const TaskSettingCore& taskSetting) + { + std::unique_lock _l(m_taskMtx); + if (m_registeredTasks.find(taskSetting.taskId) != m_registeredTasks.end()) + { + // task already registered. Return error? + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Task {} is already registered", taskSetting.taskId.id.c_str()); + return; + } + m_registeredTasks.try_emplace(taskSetting.taskId, std::make_shared(taskSetting.taskId, taskSetting)); + } + + void UnregisterTask(uint32 persistentId, const TaskId& taskId) + { + std::unique_lock _l(m_taskMtx); + auto it = m_registeredTasks.find(taskId); + if (it != m_registeredTasks.end()) + { + m_registeredTasks.erase(it); + } + else + { + // todo - return error + } + } + + bool TaskIsRegistered(uint32 persistentId, const TaskId& taskId) + { + std::unique_lock _l(m_taskMtx); + return m_registeredTasks.find(taskId) != m_registeredTasks.end(); + } + + void TaskRun(uint32 persistentId, const TaskId& taskId) + { + // todo - there is an extra parameter for controlling background/foreground running? + std::shared_ptr registeredTask = GetRegisteredTask2(persistentId, taskId); + if (registeredTask) + { + cemuLog_log(LogType::Force, "IOSU-Boss: Running task {}", taskId.id.c_str()); + registeredTask->Run(); + } + else + { + cemuLog_log(LogType::Force, "IOSU-Boss: Trying to run task {} which has not been registered", taskId.id.c_str()); + // todo - return error -> 0xA021F900 + } + } + + void StartScheduleTask(uint32 persistentId, const TaskId& taskId, bool runImmediately) + { + if (runImmediately) + { + // proper implementation is still todo + // handling automatic scheduling and task state transitions is quite complex + // for now we only run the task once + std::shared_ptr registeredTask = GetRegisteredTask2(persistentId, taskId); + TaskState state = registeredTask->GetState(); + if (state == TaskState::Stopped || state == TaskState::Initial) + { + registeredTask->Run(); + } + else + { + cemuLog_logDebug(LogType::Force, "StartScheduleTask(): No support for scheduling tasks that aren't in stopped state"); + } + } + else + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unsupported StartScheduling called for task {}", taskId.id.c_str()); + } + } + + void StopScheduleTask(uint32 persistentId, const TaskId& taskId) + { + // todo + // task scheduling is not yet implemented so this does nothing for now + } + + std::shared_ptr GetRegisteredTask2(const uint32 persistentId, const TaskId& taskId) + { + std::unique_lock _l(m_taskMtx); + auto it = m_registeredTasks.find(taskId); // todo - check persistentId as well (make it part of the key?) + if (it != m_registeredTasks.end()) + return it->second; + return nullptr; + } + + private: + void BossDaemonThread() + { + CURL* curl = curl_easy_init(); + while ( m_threadRunning ) + { + // check for tasks to run + { + std::shared_ptr task = GetNextRunableTask(); + if (task) + { + task->TaskDoRequest(curl); + cemu_assert_debug(task->GetState() != TaskState::Ready); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + curl_easy_cleanup(curl); + } + + std::shared_ptr GetNextRunableTask() + { + std::unique_lock _l(m_taskMtx); + for (auto& task : m_registeredTasks) + { + if (task.second->GetState() == TaskState::Ready) + return task.second; + } + return nullptr; + } + + std::thread m_bossDaemonThread; + std::atomic_bool m_threadRunning{ false }; + // task list + std::mutex m_taskMtx; + std::map> m_registeredTasks; + }s_bossDaemon; + + class BossMainService : public iosu::nn::IPCService + { + public: + BossMainService() : iosu::nn::IPCService("/dev/boss") {} + + nnResult ServiceCall(IPCServiceCall& serviceCall) override + { + if (serviceCall.GetServiceId() == 0) + { + switch (static_cast(serviceCall.GetCommandId())) + { + case BossCommandId::TaskIsRegistered: + return HandleTaskIsRegistered(serviceCall); + case BossCommandId::TaskRegisterA: + return HandleTaskRegister(serviceCall, false); + case BossCommandId::TaskRegisterForImmediateRunA: + return HandleTaskRegister(serviceCall, true); + case BossCommandId::TaskUnregister: + return HandleTaskUnregister(serviceCall); + case BossCommandId::TaskRun: + return HandleTaskRun(serviceCall); + case BossCommandId::TaskStartScheduling: + return HandleTaskStartScheduling(serviceCall); + case BossCommandId::TaskStopScheduling: + return HandleTaskStopScheduling(serviceCall); + case BossCommandId::TaskWaitA: + return HandleTaskWait(serviceCall); + case BossCommandId::TaskGetTurnState: + return HandleTaskGetTurnState(serviceCall); + case BossCommandId::TaskGetHttpStatusCodeA: + return HandleTaskGetHttpStatusCodeA(serviceCall); + case BossCommandId::TaskGetContentLength: + return HandleTaskGetContentLength(serviceCall); + case BossCommandId::TaskGetProcessedLength: + return HandleTaskGetProcessedLength(serviceCall); + case BossCommandId::UknA7: + return HandleUknA7(serviceCall); + case BossCommandId::DeleteDataRelated: + return HandleDeleteDataRelated(serviceCall); + case BossCommandId::StorageExist: + return HandleStorageExist(serviceCall); + case BossCommandId::StorageGetDataList: + return HandleStorageGetDataList(serviceCall); + case BossCommandId::NsDataExist: + return HandleNsDataExist(serviceCall); + case BossCommandId::NsDataRead: + return HandleNsDataRead(serviceCall); + case BossCommandId::NsDataGetSize: + return HandleNsDataGetSize(serviceCall); + case BossCommandId::NsDataDeleteFile: + return HandleNsDataDeleteFile(serviceCall, false); + case BossCommandId::NsDataDeleteFileWithHistory: + return HandleNsDataDeleteFile(serviceCall, true); + case BossCommandId::NsFinalize: + return HandleNsFinalize(serviceCall); + case BossCommandId::TitleSetNewArrivalFlag: + return HandleTitleSetNewArrivalFlag(serviceCall); + default: + cemuLog_log(LogType::Force, "Unsupported service call to /dev/boss"); + cemu_assert_unimplemented(); + break; + } + } + else + { + cemu_assert_suspicious(); + } + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + // helper function to get proper persistentId + uint32 BossGetPersistentId(uint32 persistentId) + { + if (persistentId != 0) + return persistentId; + uint32 currentPersistentId; + bool r = iosu::act::GetPersistentId(iosu::act::ACT_SLOT_CURRENT, ¤tPersistentId);; + if (!r) + return 0; + return currentPersistentId; + } + + nnResult HandleTaskIsRegistered(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + bool isRegistered = s_bossDaemon.TaskIsRegistered(persistentId, taskId); + // write response + serviceCall.WriteResponse(isRegistered ? 1 : 0); + return RESULT_SUCCESS; + } + + nnResult HandleTaskRegister(IPCServiceCall& serviceCall, bool forImmediateRun) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + IPCServiceCall::UnalignedBuffer taskSettingsBuffer = serviceCall.ReadUnalignedInputBufferInfo(); + TaskSettingCore taskSetting = taskSettingsBuffer.ReadType(); // bugged.. (but on the submission side) + if (taskSetting.persistentId == 0) + taskSetting.persistentId = persistentId; + taskSetting.taskId = taskId; + s_bossDaemon.RegisterTask(taskSetting); + return RESULT_SUCCESS; + } + + nnResult HandleTaskUnregister(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + s_bossDaemon.UnregisterTask(persistentId, taskId); + return RESULT_SUCCESS; + } + + nnResult HandleTaskRun(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + bool isForegroundRun = serviceCall.ReadParameter() != 0; // todo - handle this + s_bossDaemon.TaskRun(persistentId, taskId); + return RESULT_SUCCESS; + } + + nnResult HandleTaskStartScheduling(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + uint8 startImmediately = serviceCall.ReadParameter(); + s_bossDaemon.StartScheduleTask(persistentId, taskId, startImmediately != 0); + return RESULT_SUCCESS; + } + + nnResult HandleTaskStopScheduling(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + s_bossDaemon.StopScheduleTask(persistentId, taskId); + return RESULT_SUCCESS; + } + + nnResult HandleTaskWait(IPCServiceCall& serviceCall) + { + static_assert(sizeof(betype) == 4); + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + TaskWaitState waitState = serviceCall.ReadParameter>(); + uint32 timeout = serviceCall.ReadParameter(); + std::chrono::steady_clock::time_point endTime; + if (timeout != 0) + endTime = std::chrono::steady_clock::now() + std::chrono::seconds(timeout); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + cemu_assert_debug(registeredTask != nullptr); + if (!registeredTask) + return RESULT_NOTEXIST; + while (true) + { + // todo - make async + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + TaskState currentState = registeredTask->GetState(); + // does the state match the wait state? + if (waitState == TaskWaitState::Done) + { + if (currentState == TaskState::Done) + { + serviceCall.WriteResponse(1); // 0 -> timeout, 1 -> no timeout + return RESULT_SUCCESS; + } + } + else + { + cemuLog_log(LogType::Force, "IOSU-Boss: Unknown task wait state {}", static_cast(waitState)); + serviceCall.WriteResponse(0); + return RESULT_SUCCESS; + } + // check for timeout + if (timeout != 0 && std::chrono::steady_clock::now() >= endTime) + { + serviceCall.WriteResponse(0); + return RESULT_SUCCESS; // which error to return here? + } + } + } + + nnResult HandleTaskGetTurnState(IPCServiceCall& serviceCall) + { + static_assert(sizeof(betype) == 4); + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + TaskTurnState turnState = registeredTask ? registeredTask->GetTurnState() : TaskTurnState::Ukn; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse>(turnState); + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + nnResult HandleTaskGetHttpStatusCodeA(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + sint32 httpStatusCode = registeredTask ? registeredTask->GetHttpStatusCode() : 0; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse(httpStatusCode); + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + nnResult HandleTaskGetContentLength(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + uint32 contentLength = registeredTask ? registeredTask->GetContentLength() : 0; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse(contentLength); + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + nnResult HandleTaskGetProcessedLength(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + uint32 processedLength = registeredTask ? registeredTask->GetProcessedLength() : 0; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse(processedLength); // this the actual length? + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + /* Storage */ + + nnResult HandleStorageExist(IPCServiceCall& serviceCall) + { + static_assert(sizeof(DirectoryName) == 8); + static_assert(sizeof(betype) == 4); + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + DirectoryName directoryName = serviceCall.ReadParameter(); + StorageKind storageKind = serviceCall.ReadParameter>(); + if (!persistentId) + return RESULT_C0203880; + uint64 titleId = CafeSystem::GetForegroundTitleId(); + // todo - investigate how IOSU handles this + bool storageExists = true; + if (storageKind == StorageKind::StorageNbdl) + { + // but for now we only check if the fad.db file exists for nbdl + storageExists = m_fadDb.CheckIfStorageExists(directoryName, titleId, persistentId); + } + else + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled storage kind {}", static_cast(storageKind)); + cemu_assert_unimplemented(); + } + serviceCall.WriteResponse(storageExists ? 1 : 0); + return RESULT_SUCCESS; + } + + nnResult HandleStorageGetDataList(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); + cemu_assert_debug(ukn == 0); + IPCServiceCall::UnalignedBuffer dataListOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo(); + uint32 startOffset = serviceCall.ReadParameter(); // guessed + cemu_assert_debug(startOffset == 0); // todo - implement offset + size_t dataListSize = dataListOutputBuffer.GetSize() / sizeof(DataName); + std::vector dataListTmp; + uint64 titleId = CafeSystem::GetForegroundTitleId(); + // open FAD database + TaskId taskId(directoryName.name2.c_str()); + nnResult r = m_fadDb.Load(taskId, titleId, persistentId); + if (NN_RESULT_IS_FAILURE(r)) + return r; + m_fadDb.GetDataList(dataListTmp); + if (dataListTmp.size() > dataListSize) + dataListTmp.resize(dataListSize); + // write to output + dataListOutputBuffer.WriteData(dataListTmp.data(), dataListTmp.size() * sizeof(DataName)); + serviceCall.WriteResponse((uint32)dataListTmp.size()); + serviceCall.WriteResponse(1); // extra byte to indicate success or failure + return RESULT_SUCCESS; + } + + /* NsData */ + + nnResult HandleNsDataExist(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + return r; + bool nsDataExists = m_nsDataAccessor.Exists(); + serviceCall.WriteResponse(nsDataExists); + return RESULT_SUCCESS; + } + + nnResult HandleNsDataRead(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + IPCServiceCall::UnalignedBuffer dataOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo(); + uint64 readOffset = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + { + serviceCall.WriteResponse(0); // should we still write the bytes read in case of failure? + return r; + } + uint32 bytesRead = 0; + std::vector tmpBuffer; + tmpBuffer.resize(dataOutputBuffer.GetSize()); + bool rRead = m_nsDataAccessor.Read(tmpBuffer.data(), tmpBuffer.size(), readOffset, bytesRead); + dataOutputBuffer.WriteData(tmpBuffer.data(), tmpBuffer.size()); + serviceCall.WriteResponse(bytesRead); + // todo - handle rRead + return RESULT_SUCCESS; + } + + nnResult HandleNsDataGetSize(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + return r; + uint32 fileSize = 0; + r = m_nsDataAccessor.GetSize(fileSize); + serviceCall.WriteResponse(fileSize); + return r; + } + + nnResult HandleNsDataDeleteFile(IPCServiceCall& serviceCall, bool withHistory) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + return r; + // todo - handle withHistory + return m_nsDataAccessor.Delete(); + } + + nnResult HandleNsFinalize(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + m_nsDataAccessor.Close(); + return RESULT_SUCCESS; + } + + /* Title */ + + nnResult HandleTitleSetNewArrivalFlag(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + uint8 flagValue = serviceCall.ReadParameter(); + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled command TitleSetNewArrivalFlag"); + return RESULT_SUCCESS; + } + + /* stubbed opcodes */ + nnResult HandleUknA7(IPCServiceCall& serviceCall) + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled command A7"); + // no response data + return RESULT_SUCCESS; + } + + nnResult HandleDeleteDataRelated(IPCServiceCall& serviceCall) + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled delete command"); + // no response data + return RESULT_SUCCESS; + } + + + }; + + BossMainService s_bossService; + + class : public ::IOSUModule + { + void TitleStart() override + { + s_bossService.Start(); + s_bossDaemon.Start(); + } + void TitleStop() override + { + s_bossService.Stop(); + m_fadDb.Clear(); + m_nsDataAccessor.Close(); + } + }sIOSUModuleNNBOSS; + + IOSUModule* GetModule() + { + return static_cast(&sIOSUModuleNNBOSS); + } +} diff --git a/src/Cafe/IOSU/nn/boss/boss_service.h b/src/Cafe/IOSU/nn/boss/boss_service.h new file mode 100644 index 00000000..f52f5d4d --- /dev/null +++ b/src/Cafe/IOSU/nn/boss/boss_service.h @@ -0,0 +1,7 @@ +#pragma once +#include "Cafe/IOSU/iosu_types_common.h" + +namespace iosu::boss +{ + IOSUModule* GetModule(); +} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index c888b4fb..53468950 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -188,18 +188,43 @@ namespace iosu else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) { uint32 requestId = cmd->args[0]; - uint32 numIn = cmd->args[1]; - uint32 numOut = cmd->args[2]; + uint32 numOut = cmd->args[1]; + uint32 numIn = cmd->args[2]; IPCIoctlVector* vec = MEMPTR{ cmd->args[3] }.GetPtr(); - IPCIoctlVector* vecIn = vec + numIn; - IPCIoctlVector* vecOut = vec + 0; + IPCIoctlVector* vecOut = vec + 0; // out buffers come first + IPCIoctlVector* vecIn = vec + numOut; + cemu_assert(numIn > 0 && numOut > 0); cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull()); - IPCServiceRequest* serviceRequest = MEMPTR(vecIn->basePhys).GetPtr(); - IPCServiceResponse* serviceResponse = MEMPTR(vecOut->basePhys).GetPtr(); + IPCServiceRequestHeader* serviceRequest = MEMPTR(vecIn->basePhys).GetPtr(); + IPCServiceResponseHeader* serviceResponse = MEMPTR(vecOut->basePhys).GetPtr(); + + IOSDevHandle clientHandle = 0; // todo + IPCServiceCall serviceCall(clientHandle, serviceRequest->serviceId, serviceRequest->commandId); + +#if 0 + // log all buffers + cemuLog_log(LogType::Force, "IPC ServiceCall. In: {}, Out: {}, ServiceId: {}, CommandId: {} (0x{:x})", numIn, numOut, serviceRequest->serviceId, serviceRequest->commandId, serviceRequest->commandId); + for (size_t i = 0; i (vec[i].basePhys).GetPtr(), vec[i].size, 16); + } +#endif + + // parameter and response data is appended directly after the headers, so we add the streams without the headers + serviceCall.AddInputStream(MEMPTR(vecIn[0].basePhys).GetPtr() + sizeof(IPCServiceRequestHeader), vecIn[0].size - sizeof(IPCServiceRequestHeader)); + serviceCall.AddOutputStream(MEMPTR(vecOut[0].basePhys).GetPtr() + sizeof(IPCServiceResponseHeader), vecOut[0].size - sizeof(IPCServiceResponseHeader)); + // add remaining input/output buffers + for (size_t i = 1; i < numIn; i++) + serviceCall.AddInputStream(MEMPTR(vecIn[i].basePhys).GetPtr(), vecIn[i].size); + for (size_t i = 1; i < numOut; i++) + serviceCall.AddOutputStream(MEMPTR(vecOut[i].basePhys).GetPtr(), vecOut[i].size); + + serviceResponse->nnResultCode = (uint32)ServiceCall(serviceCall); - serviceResponse->nnResultCode = (uint32)ServiceCall(serviceRequest->serviceId, nullptr, nullptr); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.h b/src/Cafe/IOSU/nn/iosu_nn_service.h index d7d4cb01..94bd03f5 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.h +++ b/src/Cafe/IOSU/nn/iosu_nn_service.h @@ -73,25 +73,239 @@ namespace iosu SysAllocator _m_msgBuffer; }; - struct IPCServiceRequest - { - uint32be ukn00; - uint32be serviceId; - uint32be ukn08; - uint32be commandId; - }; - - static_assert(sizeof(IPCServiceRequest) == 0x10); - - struct IPCServiceResponse - { - uint32be nnResultCode; - }; - - // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, ? + // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, /dev/boss and most nn services class IPCService { + struct IPCServiceRequestHeader + { + uint32be ukn00; + uint32be serviceId; + uint32be ukn08; + uint32be commandId; + }; + + static_assert(sizeof(IPCServiceRequestHeader) == 0x10); + + struct IPCServiceResponseHeader + { + uint32be nnResultCode; + }; public: + class IPCParameterStream // input stream for parameters + { + public: + IPCParameterStream() = default; + IPCParameterStream(void* data, uint32 size) : m_data((uint8_t*)data), m_size(size) {} + + template + T ReadParameter(bool& hasError) + { + hasError = false; + if (m_readIndex + sizeof(T) > m_size) + { + hasError = true; + return T{}; + } + T value; + memcpy(&value, &m_data[m_readIndex], sizeof(T)); + m_readIndex += sizeof(T); + return value; + } + + uint8* GetData() { return m_data; } + uint32 GetSize() const { return m_size; } + + private: + uint8* m_data{nullptr}; + uint32 m_size{0}; + uint32 m_readIndex{0}; + }; + + class IPCResponseStream // output stream for response data + { + public: + IPCResponseStream() = default; + IPCResponseStream(void* data, uint32 size) : m_data((uint8*)data), m_size(size) {} + + template + void Write(const T& value) + { + if (m_writtenSize + sizeof(T) > m_size) + { + m_hasError = true; + return; + } + memcpy(&m_data[m_writtenSize], &value, sizeof(T)); + m_writtenSize += sizeof(T); + } + + uint8* GetData() { return m_data; } + uint32 GetSize() const { return m_size; } + + private: + uint8* m_data{nullptr}; + uint32 m_writtenSize{0}; + uint32 m_size{0}; + bool m_hasError{false}; + }; + + class IPCServiceCall + { + struct LargeBufferHeader + { + uint32be alignedSize; // size of the aligned (middle) part of the buffer + uint8be ukn4; + uint8be ukn5; + uint8be headSize; + uint8be tailSize; + }; + static_assert(sizeof(LargeBufferHeader) == 8); + public: + struct UnalignedBuffer + { + UnalignedBuffer(bool isOutput, std::span head, std::span middle, std::span tail) : m_isOutput(isOutput) + { + headPtr = head.data(); + headSize = (uint32)head.size(); + middlePtr = middle.data(); + middleSize = (uint32)middle.size(); + tailPtr = tail.data(); + tailSize = (uint32)tail.size(); + }; + + template + T ReadType() + { + cemu_assert(!m_isOutput); + T v; + memcpy((uint8_t*)&v, headPtr, headSize); + memcpy((uint8_t*)&v + headSize, middlePtr, middleSize); + memcpy((uint8_t*)&v + headSize + middleSize, tailPtr, tailSize); + return v; + } + + void WriteData(void* data, size_t size) + { + if (size == 0) + return; + cemu_assert(m_isOutput); + cemu_assert((headSize + middleSize + tailSize) >= size); + size_t bytesToCopy = std::min(size, headSize); + memcpy(headPtr, data, bytesToCopy); + size -= bytesToCopy; + if (size > 0) + { + bytesToCopy = std::min(size, middleSize); + memcpy(middlePtr, (uint8_t*)data + headSize, bytesToCopy); + size -= bytesToCopy; + } + if (size > 0) + { + bytesToCopy = std::min(size, tailSize); + memcpy(tailPtr, (uint8_t*)data + headSize + middleSize, bytesToCopy); + } + } + + size_t GetSize() const + { + return headSize + middleSize + tailSize; + } + + private: + void* headPtr; + uint32 headSize; + void* middlePtr; // aligned + uint32 middleSize; + void* tailPtr; + uint32 tailSize; + bool m_isOutput; + }; + + IPCServiceCall(IOSDevHandle clientHandle, uint32 serviceId, uint32 commandId) : m_clientHandle(clientHandle), m_serviceId(serviceId), m_commandId(commandId) + { + } + + void AddInputStream(void* data, uint32 size) + { + cemu_assert(m_paramStreamArraySize < 4); + m_paramStreamArray[m_paramStreamArraySize++] = IPCParameterStream(data, size); + } + + void AddOutputStream(void* data, uint32 size) + { + cemu_assert(m_responseStreamArraySize < 4); + m_responseStreamArray[m_responseStreamArraySize++] = IPCResponseStream(data, size); + } + + uint32 GetServiceId() const + { + return m_serviceId; + } + + uint32 GetCommandId() const + { + return m_commandId; + } + + template + T ReadParameter() + { + // read only from stream 0 for now + return m_paramStreamArray[0].ReadParameter(m_hasError); + } + + UnalignedBuffer ReadUnalignedInputBufferInfo() + { + // how large/unaligned buffers work: + // Instead of appending the data into the parameter stream, there are two separate buffers created: + // 1. A ioctl vector that points directly to the aligned part of the original buffer. Both the pointer and the size are aligned + // 2. A ioctl vector with an allocated up-to-128byte buffer that holds any unaligned head or tail data (e.g. anything that isn't occupying the full 64 byte alignment) + // The buffer layout is then also serialized into the parameter stream + LargeBufferHeader header = ReadParameter(); + // get aligned buffer part + void* alignedBuffer = m_paramStreamArray[m_inputBufferIndex+0].GetData(); + cemu_assert(m_paramStreamArray[m_inputBufferIndex+0].GetSize() == header.alignedSize); + // get head and tail buffer parts + uint8* unalignedDataBuffer = m_paramStreamArray[m_inputBufferIndex+1].GetData(); + cemu_assert((header.headSize + header.tailSize) <= m_paramStreamArray[m_inputBufferIndex+1].GetSize()); + UnalignedBuffer largeBuffer(false, {(uint8*)unalignedDataBuffer, header.headSize}, {(uint8*)alignedBuffer, header.alignedSize}, {(uint8*)unalignedDataBuffer + header.headSize, header.tailSize}); + m_inputBufferIndex += 2; // if there is no unaligned data then are both buffers still present? + return largeBuffer; + } + + UnalignedBuffer ReadUnalignedOutputBufferInfo() + { + LargeBufferHeader header = ReadParameter(); + // get aligned buffer part + void* alignedBuffer = m_responseStreamArray[m_outputBufferIndex+0].GetData(); + cemu_assert(m_responseStreamArray[m_outputBufferIndex+0].GetSize() == header.alignedSize); + // get head and tail buffer parts + uint8* unalignedDataBuffer = m_responseStreamArray[m_outputBufferIndex+1].GetData(); + cemu_assert((header.headSize + header.tailSize) <= m_responseStreamArray[m_outputBufferIndex+1].GetSize()); + UnalignedBuffer largeBuffer(true, {(uint8*)unalignedDataBuffer, header.headSize}, {(uint8*)alignedBuffer, header.alignedSize}, {(uint8*)unalignedDataBuffer + header.headSize, header.tailSize}); + m_outputBufferIndex += 2; // if there is no unaligned data then are both buffers still present? + return largeBuffer; + } + + template + void WriteResponse(const T& value) + { + m_responseStreamArray[0].Write(value); + } + + private: + IOSDevHandle m_clientHandle; + uint32 m_serviceId; + uint32 m_commandId; + IPCParameterStream m_paramStreamArray[4]{}; + size_t m_paramStreamArraySize{0}; + IPCResponseStream m_responseStreamArray[4]{}; + size_t m_responseStreamArraySize{0}; + bool m_hasError{false}; + sint8 m_inputBufferIndex{1}; + sint8 m_outputBufferIndex{1}; + }; + IPCService(std::string_view devicePath) : m_devicePath(devicePath) {}; virtual ~IPCService() {}; @@ -105,7 +319,7 @@ namespace iosu } - virtual nnResult ServiceCall(uint32 serviceId, void* request, void* response) + virtual nnResult ServiceCall(IPCServiceCall& serviceCall) { cemu_assert_unimplemented(); return 0; diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp index e7830cd8..564b787e 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp @@ -206,8 +206,12 @@ namespace GX2 void GX2SetTVGamma(float gamma) { - if (abs(gamma - 1.0f) > 0.01f) - cemuLog_logDebug(LogType::Force, "TV gamma set to {} which is not supported", gamma); + LatteGPUState.tvGamma = (1.0f - gamma); + } + + void GX2SetDRCGamma(float gamma) + { + LatteGPUState.drcGamma = (1.0f - gamma); } bool GX2GetLastFrame(uint32 deviceId, GX2Texture* textureOut) @@ -307,6 +311,7 @@ namespace GX2 cafeExportRegister("gx2", GX2SetTVBuffer, LogType::GX2); cafeExportRegister("gx2", GX2SetTVGamma, LogType::GX2); + cafeExportRegister("gx2", GX2SetDRCGamma, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrame, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrameGammaA, LogType::GX2); diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp index ce85048e..681889dc 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp @@ -161,7 +161,7 @@ void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 src if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight ) { - cemu_assert_debug(false); + cemuLog_logDebugOnce(LogType::Force, "GX2CopySurface: Mismatching mip resolution"); return; } // handle format diff --git a/src/Cafe/OS/libs/nn_ac/nn_ac.h b/src/Cafe/OS/libs/nn_ac/nn_ac.h index b8827200..520e1452 100644 --- a/src/Cafe/OS/libs/nn_ac/nn_ac.h +++ b/src/Cafe/OS/libs/nn_ac/nn_ac.h @@ -1 +1,6 @@ -void nnAc_load(); \ No newline at end of file +void nnAc_load(); + +namespace nn_ac +{ + nnResult IsApplicationConnected(uint8be* connected); +} diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index 2a05fa7a..c440858d 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -1,42 +1,34 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/nn_client_service.h" #include "Cafe/OS/libs/nn_act/nn_act.h" -#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/nn_ac/nn_ac.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" -#include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" +#include "Common/CafeString.h" +#include "Cafe/IOSU/nn/boss/boss_common.h" namespace nn { - typedef uint32 Result; namespace boss { -#define bossPrepareRequest() \ -StackAllocator _buf_bossRequest; \ -StackAllocator _buf_bufferVector; \ -iosuBossCemuRequest_t* bossRequest = _buf_bossRequest.GetPointer(); \ -ioBufferVector_t* bossBufferVector = _buf_bufferVector.GetPointer(); \ -memset(bossRequest, 0, sizeof(iosuBossCemuRequest_t)); \ -memset(bossBufferVector, 0, sizeof(ioBufferVector_t)); \ -bossBufferVector->buffer = (uint8*)bossRequest; + IPCServiceClient s_ipcClient; + + static constexpr BossResult resultSuccess = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + static constexpr BossResult resultInvalidParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); + static constexpr BossResult resultUknA0220300 = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_BOSS, 0x20300); SysAllocator g_mutex; sint32 g_initCounter = 0; bool g_isInitialized = false; - struct VTableEntry - { - uint16be offsetA{0}; - uint16be offsetB{0}; - MEMPTR ptr; - }; - static_assert(sizeof(VTableEntry) == 8); - #define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) + SysAllocator s_ipcBuffer; + constexpr uint32 BOSS_MEM_MAGIC = 0xCAFE4321; template @@ -62,15 +54,15 @@ bossBufferVector->buffer = (uint8*)bossRequest; coreinit::_weak_MEMFreeToDefaultHeap(basePtr); } - Result Initialize() // Initialize__Q2_2nn4bossFv + BossResult Initialize() // Initialize__Q2_2nn4bossFv { coreinit::OSLockMutex(&g_mutex); - Result result = 0; + BossResult result = 0; if(g_initCounter == 0) { g_isInitialized = true; - // IPC init here etc. - result = 0x200080; // init result + s_ipcClient.Initialize("/dev/boss", s_ipcBuffer.GetPtr(), s_ipcBuffer.GetByteSize()); + result = resultSuccess; } g_initCounter++; coreinit::OSUnlockMutex(&g_mutex); @@ -89,8 +81,8 @@ bossBufferVector->buffer = (uint8*)bossRequest; cemuLog_log(LogType::Force, "nn_boss: Finalize() called without corresponding Initialize()"); if(g_initCounter == 1) { + s_ipcClient.Shutdown(); g_isInitialized = false; - // IPC deinit here etc. } g_initCounter--; coreinit::OSUnlockMutex(&g_mutex); @@ -98,579 +90,400 @@ bossBufferVector->buffer = (uint8*)bossRequest; uint32 GetBossState(PPCInterpreter_t* hCPU) { - cemuLog_logDebug(LogType::Force, "nn_boss.GetBossState() - stub"); + cemuLog_logDebugOnce(LogType::Force, "nn_boss.GetBossState() - stub"); return 7; } - struct TitleId + /* TitleId */ + + bool TitleId::IsValid(TitleId* _thisptr) { - uint64be u64{}; + return _thisptr->u64 != 0; + } - static TitleId* ctor(TitleId* _thisptr, uint64 titleId) + TitleId* TitleId::ctorDefault(TitleId* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = 0; + return _thisptr; + } + + TitleId* TitleId::ctorFromTitleId(TitleId* _thisptr, uint64 titleId) // __ct__Q3_2nn4boss7TitleIDFUL + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId; + return _thisptr; + } + + TitleId* TitleId::ctorCopy(TitleId* _thisptr, TitleId* titleId) // __ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId->u64; + return _thisptr; + } + + bool TitleId::operator_ne(TitleId* _thisptr, TitleId* titleId) + { + return _thisptr->u64 != titleId->u64; + } + /* TaskId */ + + TaskId* TaskId::ctorDefault(TaskId* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->id.data[0] = 0; + return _thisptr; + } + + TaskId* TaskId::ctorFromString(TaskId* _thisptr, const char* taskId) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->id.assign(taskId); + return _thisptr; + } + + /* Title */ + + Title* Title::ctor(Title* _this) + { + if (!_this) + _this = boss_new(); + *_this = {}; + _this->vTablePtr = s_titleVTable; + return _this; + } + + void Title::dtor(Title* _this, uint32 options) + { + if (_this && (options & 1)) + boss_delete(_this); + } + + void Title::InitVTable() + { + s_titleVTable->rtti.ptr = nullptr; // todo + s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); + } + + /* DirectoryName */ + + DirectoryName* DirectoryName::ctor(DirectoryName* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<DirectoryName>(); + _thisptr->name2.ClearAllBytes(); + return _thisptr; + } + + const char* DirectoryName::operator_const_char(DirectoryName* _thisptr) + { + return _thisptr->name2.c_str(); + } + + /* BossAccount */ + + BossAccount* BossAccount::ctor(BossAccount* _this, uint32 accountId) + { + if (!_this) + _this = boss_new<BossAccount>(); + _this->accountId = accountId; + _this->vTablePtr = s_VTable; + return _this; + } + + void BossAccount::dtor(BossAccount* _this, uint32 options) + { + if(_this && options & 1) + boss_delete(_this); + } + + void BossAccount::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); + } + + /* TaskSetting */ + + TaskSetting* TaskSetting::ctor(TaskSetting* _thisptr) + { + if(!_thisptr) + _thisptr = boss_new<TaskSetting>(); + _thisptr->vTablePtr = s_VTable; + InitializeSetting(_thisptr); + return _thisptr; + } + + void TaskSetting::dtor(TaskSetting* _this, uint32 options) + { + if(options & 1) + boss_delete(_this); + } + + bool TaskSetting::IsPrivileged(TaskSetting* _thisptr) + { + const TaskType taskType = _thisptr->taskType; + return taskType == TaskType::RawDlTaskSetting_1 || taskType == TaskType::RawDlTaskSetting_9 || taskType == TaskType::PlayLogUploadTaskSetting; + } + + void TaskSetting::InitializeSetting(TaskSetting* _thisptr) + { + memset(_thisptr, 0x00, 0x1000); + _thisptr->persistentId = 0; + _thisptr->ukn08 = 0; + _thisptr->ukn0C = 0; + _thisptr->priority = 125; + _thisptr->intervalB = 28800; + _thisptr->lifeTime.high = 0; + _thisptr->lifeTime.low = 0x76A700; + } + + void TaskSetting::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } + + /* NetTaskSetting */ + + NetTaskSetting* NetTaskSetting::ctor(NetTaskSetting* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<NetTaskSetting>(); + TaskSetting::ctor(_thisptr); + _thisptr->httpTimeout = 120; + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + BossResult NetTaskSetting::AddCaCert(NetTaskSetting* _thisptr, const char* name) + { + if(name == nullptr || strnlen(name, 0x80) == 0x80) { - if (!_thisptr) - _thisptr = boss_new<TitleId>(); - _thisptr->u64 = titleId; - return _thisptr; + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddCaCert: name size is invalid"); + return resultInvalidParam; } - static TitleId* ctor(TitleId* _thisptr) - { - return ctor(_thisptr, 0); - } + cemu_assert_unimplemented(); - static bool IsValid(TitleId* _thisptr) - { - return _thisptr->u64 != 0; - } + return 0xA0220D00; + } - static TitleId* ctor1(TitleId* _thisptr, uint32 filler, uint64 titleId) - { - return ctor(_thisptr); - } + BossResult NetTaskSetting::SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) // input is uint8[512]? + { + memcpy(_thisptr->serviceToken.data, serviceToken, _thisptr->serviceToken.Size()); + return resultSuccess; + } - static TitleId* ctor2(TitleId* _thisptr, uint32 filler, uint64 titleId) + BossResult NetTaskSetting::AddInternalCaCert(NetTaskSetting* _thisptr, char certId) + { + for(int i = 0; i < 3; i++) { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_ctor2(0x{:x})", MEMPTR(_thisptr).GetMPTR()); - if (!_thisptr) + if(_thisptr->internalCaCert[i] == 0) { - // _thisptr = new Task_t - assert_dbg(); + _thisptr->internalCaCert[i] = (uint8)certId; + return resultSuccess; } - - _thisptr->u64 = titleId; - return _thisptr; } + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: Cannot store more than 3 certificates"); + return 0xA0220D00; + } - static TitleId* ctor3(TitleId* _thisptr, TitleId* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_cctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); - if (!_thisptr) - _thisptr = boss_new<TitleId>(); - _thisptr->u64 = titleId->u64; - return _thisptr; - } - - static bool operator_ne(TitleId* _thisptr, TitleId* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_operator_ne(0x{:x})", MEMPTR(_thisptr).GetMPTR()); - return _thisptr->u64 != titleId->u64; - } - }; - static_assert(sizeof(TitleId) == 8); - - struct TaskId + void NetTaskSetting::SetInternalClientCert(NetTaskSetting* _thisptr, char certId) { - char id[0x8]{}; + _thisptr->internalClientCert = (uint8)certId; + } - static TaskId* ctor(TaskId* _thisptr) - { - if(!_thisptr) - _thisptr = boss_new<TaskId>(); - _thisptr->id[0] = '\0'; - return _thisptr; - } - }; - static_assert(sizeof(TaskId) == 8); - - struct Title + void NetTaskSetting::InitVTable() { - uint32be accountId{}; // 0x00 - TitleId titleId{}; // 0x8 - MEMPTR<void> vTablePtr{}; // 0x10 + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } - struct VTable - { - VTableEntry rtti; - VTableEntry dtor; - }; - static inline SysAllocator<VTable> s_titleVTable; + /* NbdlTaskSetting */ - static Title* ctor(Title* _this) - { - if (!_this) - _this = boss_new<Title>(); - *_this = {}; - _this->vTablePtr = s_titleVTable; - return _this; - } - - static void dtor(Title* _this, uint32 options) - { - if (_this && (options & 1)) - boss_delete(_this); - } - - static void InitVTable() - { - s_titleVTable->rtti.ptr = nullptr; // todo - s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); - } - }; - static_assert(sizeof(Title) == 0x18); - - struct DirectoryName + NbdlTaskSetting* NbdlTaskSetting::ctor(NbdlTaskSetting* _thisptr) { - char name[0x8]{}; + if (!_thisptr) + _thisptr = boss_new<NbdlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } - static DirectoryName* ctor(DirectoryName* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<DirectoryName>(); - memset(_thisptr->name, 0x00, 0x8); - return _thisptr; - } - - static const char* operator_const_char(DirectoryName* _thisptr) - { - return _thisptr->name; - } - }; - static_assert(sizeof(DirectoryName) == 8); - - struct BossAccount // the actual class name is "Account" and while the boss namespace helps us separate this from Account(.h) we use an alternative name to avoid confusion + BossResult NbdlTaskSetting::Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* bossDirectory) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 { - struct VTable - { - VTableEntry rtti; - VTableEntry dtor; - }; - static inline SysAllocator<VTable> s_VTable; + if (!bossCode || !_thisptr->nbdl.bossCode.CanHoldString(bossCode)) + return resultInvalidParam; + if (bossDirectory && !_thisptr->nbdl.bossDirectory.CanHoldString(bossDirectory)) + return resultInvalidParam; // directory is optional, but if a string is passed it must fit + _thisptr->nbdl.bossCode.assign(bossCode); + if (bossDirectory) + _thisptr->nbdl.bossDirectory.assign(bossDirectory); + _thisptr->nbdl.directorySizeLimitHigh = (uint32be)(directorySizeLimit >> 32); + _thisptr->nbdl.directorySizeLimitLow = (uint32be)(directorySizeLimit & 0xFFFFFFFF); + _thisptr->taskType = TaskType::NbdlTaskSetting; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + } - uint32be accountId; - MEMPTR<void> vTablePtr; - - static BossAccount* ctor(BossAccount* _this, uint32 accountId) - { - if (!_this) - _this = boss_new<BossAccount>(); - _this->accountId = accountId; - _this->vTablePtr = s_VTable; - return _this; - } - - static void dtor(BossAccount* _this, uint32 options) - { - if(_this && options & 1) - boss_delete(_this); - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); - } - - }; - static_assert(sizeof(BossAccount) == 8); - - struct TaskSetting + BossResult NbdlTaskSetting::SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) { - static const uint32 kBossCode = 0x7C0; - static const uint32 kBossCodeLen = 0x20; + if (!fileName || !_thisptr->nbdl.fileName.CanHoldString(fileName)) + return resultInvalidParam; + _thisptr->nbdl.fileName.assign(fileName); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + } - static const uint32 kDirectorySizeLimit = 0x7F0; - static const uint32 kDirectoryName = 0x7E0; - static const uint32 kDirectoryNameLen = 0x8; - - //static const uint32 kFileName = 0x7F8; - static const uint32 kNbdlFileName = 0x7F8; - static const uint32 kFileNameLen = 0x20; - - static const uint32 kURL = 0x48; - static const uint32 kURLLen = 0x100; - - static const uint32 kClientCert = 0x41; - static const uint32 kCACert = 0x188; - - static const uint32 kServiceToken = 0x590; - static const uint32 kServiceTokenLen = 0x200; - - uint8 settings[0x1000]; - MEMPTR<void> vTablePtr; // +0x1000 - - struct VTableTaskSetting - { - VTableEntry rtti; - VTableEntry dtor; - VTableEntry RegisterPreprocess; - VTableEntry unk1; - }; - static inline SysAllocator<VTableTaskSetting> s_VTable; - - static TaskSetting* ctor(TaskSetting* _thisptr) - { - if(!_thisptr) - _thisptr = boss_new<TaskSetting>(); - _thisptr->vTablePtr = s_VTable; - InitializeSetting(_thisptr); - return _thisptr; - } - - static void dtor(TaskSetting* _this, uint32 options) - { - cemuLog_logDebug(LogType::Force, "nn::boss::TaskSetting::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); - if(options & 1) - boss_delete(_this); - } - - static bool IsPrivileged(TaskSetting* _thisptr) - { - const uint16 value = *(uint16be*)&_thisptr->settings[0x28]; - return value == 1 || value == 9 || value == 5; - } - - static void InitializeSetting(TaskSetting* _thisptr) - { - memset(_thisptr, 0x00, sizeof(TaskSetting::settings)); - *(uint32*)&_thisptr->settings[0x0C] = 0; - *(uint8*)&_thisptr->settings[0x2A] = 0x7D; // timeout? - *(uint32*)&_thisptr->settings[0x30] = 0x7080; - *(uint32*)&_thisptr->settings[0x8] = 0; - *(uint32*)&_thisptr->settings[0x38] = 0; - *(uint32*)&_thisptr->settings[0x3C] = 0x76A700; - *(uint32*)&_thisptr->settings[0] = 0x76A700; - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - } - }; - static_assert(sizeof(TaskSetting) == 0x1004); - static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000); - - struct NetTaskSetting : TaskSetting + void NbdlTaskSetting::InitVTable() { - // 0x188 cert1 + 0x188 cert2 + 0x188 cert3 - // 0x190 AddCaCert (3times) char cert[0x80]; - // SetConnectionSetting - // SetFirstLastModifiedTime + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } - struct VTableNetTaskSetting : public VTableTaskSetting - { }; - static inline SysAllocator<VTableNetTaskSetting> s_VTable; + /* RawUlTaskSetting */ - static Result AddCaCert(NetTaskSetting* _thisptr, const char* name) - { - if(name == nullptr || strnlen(name, 0x80) == 0x80) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddCaCert: name size is invalid"); - return 0xC0203780; - } - - cemu_assert_unimplemented(); - - return 0xA0220D00; - } - - static NetTaskSetting* ctor(NetTaskSetting* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<NetTaskSetting>(); - TaskSetting::ctor(_thisptr); - *(uint32*)&_thisptr->settings[0x18C] = 0x78; - _thisptr->vTablePtr = s_VTable; - return _thisptr; - } - - static Result SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetServiceToken(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); - cemuLog_logDebug(LogType::Force, "\t->{}", fmt::ptr(serviceToken)); - memcpy(&_thisptr->settings[TaskSetting::kServiceToken], serviceToken, TaskSetting::kServiceTokenLen); - return 0x200080; - } - - static Result AddInternalCaCert(NetTaskSetting* _thisptr, char certId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); - - uint32 location = TaskSetting::kCACert; - for(int i = 0; i < 3; ++i) - { - if(_thisptr->settings[location] == 0) - { - _thisptr->settings[location] = (uint8)certId; - return 0x200080; - } - - location += TaskSetting::kCACert; - } - - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: can't store certificate"); - return 0xA0220D00; - } - - static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetInternalClientCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); - _thisptr->settings[TaskSetting::kClientCert] = (uint8)certId; - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - } - }; - static_assert(sizeof(NetTaskSetting) == 0x1004); - - struct NbdlTaskSetting : NetTaskSetting + RawUlTaskSetting* RawUlTaskSetting::ctor(RawUlTaskSetting* _thisptr) { - struct VTableNbdlTaskSetting : public VTableNetTaskSetting - { - VTableEntry rttiNetTaskSetting; // unknown - }; - static_assert(sizeof(VTableNbdlTaskSetting) == 8*5); - static inline SysAllocator<VTableNbdlTaskSetting> s_VTable; + if (!_thisptr) + _thisptr = boss_new<RawUlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + _thisptr->ukRaw1 = 0; + _thisptr->ukRaw2 = 0; + _thisptr->ukRaw3 = 0; + memset(_thisptr->rawSpace, 0x00, 0x200); + return _thisptr; + } - static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<NbdlTaskSetting>(); - NetTaskSetting::ctor(_thisptr); - _thisptr->vTablePtr = s_VTable; - return _thisptr; - } - - static Result Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* directoryName) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 - { - if(!bossCode || strnlen(bossCode, TaskSetting::kBossCodeLen) == TaskSetting::kBossCodeLen) - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - - if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - - strncpy((char*)&_thisptr->settings[TaskSetting::kBossCode], bossCode, TaskSetting::kBossCodeLen); - - *(uint64be*)&_thisptr->settings[TaskSetting::kDirectorySizeLimit] = directorySizeLimit; // uint64be - if(directoryName) - strncpy((char*)&_thisptr->settings[TaskSetting::kDirectoryName], directoryName, TaskSetting::kDirectoryNameLen); - - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); - } - - static Result SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_t_SetFileName(0x{:08x}, {})", MEMPTR(_thisptr).GetMPTR(), fileName ? fileName : "\"\""); - if (!fileName || strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - - strncpy((char*)&_thisptr->settings[TaskSetting::kNbdlFileName], fileName, TaskSetting::kFileNameLen); - // also sets byte at +0x817 to zero? - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(NbdlTaskSetting) == 0x1004); - - struct RawUlTaskSetting : NetTaskSetting + void RawUlTaskSetting::dtor(RawUlTaskSetting* _this, uint32 options) { - uint32be ukRaw1; // 0x1004 - uint32be ukRaw2; // 0x1008 - uint32be ukRaw3; // 0x100C - uint8 rawSpace[0x200]; // 0x1010 + cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); + } - struct VTableRawUlTaskSetting : public VTableNetTaskSetting - { - VTableEntry rttiNetTaskSetting; // unknown - }; - static_assert(sizeof(VTableRawUlTaskSetting) == 8*5); - static inline SysAllocator<VTableRawUlTaskSetting> s_VTable; - - static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<RawUlTaskSetting>(); - NetTaskSetting::ctor(_thisptr); - _thisptr->vTablePtr = s_VTable; - _thisptr->ukRaw1 = 0; - _thisptr->ukRaw2 = 0; - _thisptr->ukRaw3 = 0; - memset(_thisptr->rawSpace, 0x00, 0x200); - return _thisptr; - } - - static void dtor(RawUlTaskSetting* _this, uint32 options) - { - cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(RawUlTaskSetting) == 0x1210); - - struct RawDlTaskSetting : NetTaskSetting + void RawUlTaskSetting::InitVTable() { - struct VTableRawDlTaskSetting : public VTableNetTaskSetting - { - VTableEntry rttiNetTaskSetting; // unknown - }; - static_assert(sizeof(VTableRawDlTaskSetting) == 8*5); - static inline SysAllocator<VTableRawDlTaskSetting> s_VTable; + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } - static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr) - { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(_thisptr).GetMPTR()); - if (!_thisptr) - _thisptr = boss_new<RawDlTaskSetting>(); - NetTaskSetting::ctor(_thisptr); - _thisptr->vTablePtr = s_VTable; - return _thisptr; - } + /* RawDlTaskSetting */ - static Result Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) - { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_Initialize(0x{:x}, 0x{:x}, {}, {}, 0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); - if (!url) - { - return 0xC0203780; - } - - if (strnlen(url, TaskSetting::kURLLen) == TaskSetting::kURLLen) - { - return 0xC0203780; - } - - cemuLog_logDebug(LogType::Force, "\t-> url: {}", url); - - if (fileName && strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) - { - return 0xC0203780; - } - - if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) - { - return 0xC0203780; - } - - strncpy((char*)_thisptr + TaskSetting::kURL, url, TaskSetting::kURLLen); - _thisptr->settings[0x147] = '\0'; - - if (fileName) - strncpy((char*)_thisptr + 0x7D0, fileName, TaskSetting::kFileNameLen); - else - strncpy((char*)_thisptr + 0x7D0, "rawcontent.dat", TaskSetting::kFileNameLen); - _thisptr->settings[0x7EF] = '\0'; - - cemuLog_logDebug(LogType::Force, "\t-> filename: {}", (char*)_thisptr + 0x7D0); - - if (directoryName) - { - strncpy((char*)_thisptr + 0x7C8, directoryName, TaskSetting::kDirectoryNameLen); - _thisptr->settings[0x7CF] = '\0'; - cemuLog_logDebug(LogType::Force, "\t-> directoryName: {}", (char*)_thisptr + 0x7C8); - } - - _thisptr->settings[0x7C0] = newArrival; - _thisptr->settings[0x7C1] = led; - *(uint16be*)&_thisptr->settings[0x28] = 0x3; - return 0x200080; - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(RawDlTaskSetting) == 0x1004); - - struct PlayReportSetting : RawUlTaskSetting + RawDlTaskSetting* RawDlTaskSetting::ctor(RawDlTaskSetting* _thisptr) { - MEMPTR<uint8> ukn1210_ptr; // 0x1210 - uint32be ukn1214_size; // 0x1214 - uint32be ukPlay3; // 0x1218 - uint32be ukPlay4; // 0x121C + if (!_thisptr) + _thisptr = boss_new<RawDlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } - struct VTablePlayReportSetting : public VTableRawUlTaskSetting - {}; - static_assert(sizeof(VTablePlayReportSetting) == 8*5); - static inline SysAllocator<VTablePlayReportSetting> s_VTable; + BossResult RawDlTaskSetting::Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* bossDirectory) + { + if (!url || !_thisptr->url.CanHoldString(url)) + return resultInvalidParam; + cemuLog_logDebug(LogType::Force, "RawDlTaskSetting::Initialize url: {}", url); + if (fileName && !_thisptr->rawDl.fileName.CanHoldString(fileName)) + return resultInvalidParam; // fileName is optional, but if a string is passed it must fit + if (!bossDirectory || !_thisptr->rawDl.bossDirectory.CanHoldString(bossDirectory)) + return resultInvalidParam; - static PlayReportSetting* ctor(PlayReportSetting* _this) + _thisptr->url.assign(url); + _thisptr->rawDl.fileName.assign(fileName ? fileName : "rawcontent.dat"); + _thisptr->rawDl.bossDirectory.assign(bossDirectory); + _thisptr->rawDl.newArrival = newArrival; + _thisptr->rawDl.led = led; + _thisptr->taskType = TaskType::RawDlTaskSetting_3; + return resultSuccess; + } + + void RawDlTaskSetting::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + + /* PlayReportSetting */ + + PlayReportSetting* PlayReportSetting::ctor(PlayReportSetting* _this) + { + if(!_this) + _this = boss_new<PlayReportSetting>(); + RawUlTaskSetting::ctor(_this); + _this->vTablePtr = s_VTable; + _this->ukn1210_ptr = nullptr; + _this->ukn1214_size = 0; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; + return _this; + } + + void PlayReportSetting::dtor(PlayReportSetting* _this, uint32 options) + { + RawUlTaskSetting::dtor(_this, 0); + if(options&1) + boss_delete(_this->ukn1210_ptr.GetPtr()); + } + + void PlayReportSetting::Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) + { + if (!ptr || size == 0 || size > 0x19000) { - if(!_this) - _this = boss_new<PlayReportSetting>(); - RawUlTaskSetting::ctor(_this); - _this->vTablePtr = s_VTable; - _this->ukn1210_ptr = nullptr; - _this->ukn1214_size = 0; - _this->ukPlay3 = 0; - _this->ukPlay4 = 0; - return _this; + cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); + return; } - static void dtor(PlayReportSetting* _this, uint32 options) - { - RawUlTaskSetting::dtor(_this, 0); - if(options&1) - boss_delete(_this->ukn1210_ptr.GetPtr()); - } + *ptr = 0; - static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) - { - if(!ptr || size == 0 || size > 0x19000) - { - cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); - return; - } + _this->taskType = TaskType::PlayReportSetting; + _this->mode |= 3; + _this->rawUl.optionValue |= 2; + _this->permission |= 0xA; - *ptr = 0; + _this->ukn1210_ptr = ptr; + _this->ukn1214_size = size; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; - *(uint16be*)&_this->settings[0x28] = 6; - *(uint16be*)&_this->settings[0x2B] |= 0x3; - *(uint16be*)&_this->settings[0x2C] |= 0xA; - *(uint32be*)&_this->settings[0x7C0] |= 2; + _this->AddInternalCaCert(_this, 102); + _this->SetInternalClientCert(_this, 1); - _this->ukn1210_ptr = ptr; - _this->ukn1214_size = size; - _this->ukPlay3 = 0; - _this->ukPlay4 = 0; + // todo - incomplete + } - // TODO - } + bool PlayReportSetting::Set(PlayReportSetting* _this, const char* keyname, uint32 value) + { + // TODO + return true; + } - static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value) - { - // TODO - return true; - } + void PlayReportSetting::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(PlayReportSetting) == 0x1220); + /* Task */ struct Task { @@ -681,44 +494,42 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static inline SysAllocator<VTableTask> s_vTable; - uint32be accountId; // 0x00 + uint32be persistentId; // 0x00 uint32be uk2; // 0x04 TaskId taskId; // 0x08 TitleId titleId; // 0x10 MEMPTR<VTableTask> vTablePtr; // 0x18 uint32be padding; // 0x1C - static Result Initialize1(Task* _thisptr, const char* taskId, uint32 accountId) // Initialize__Q3_2nn4boss4TaskFPCcUi + static BossResult Initialize1(Task* _thisptr, const char* taskId, uint32 persistentId) // Initialize__Q3_2nn4boss4TaskFPCcUi { - if(!taskId || strnlen(taskId, 0x8) == 8) - { - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - } - _thisptr->accountId = accountId; - strncpy(_thisptr->taskId.id, taskId, 0x08); + if (!taskId || !_thisptr->taskId.id.CanHoldString(taskId)) + return resultInvalidParam; + _thisptr->persistentId = persistentId; + _thisptr->taskId.id.assign(taskId); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - static Result Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc + static BossResult Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc { - const uint32 accountId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); - return Initialize1(_thisptr, taskId, accountId); + const uint32 persistentId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); + return Initialize1(_thisptr, taskId, persistentId); } - static Result Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc + static BossResult Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc { return Initialize1(_thisptr, taskId, 0); } - static Task* ctor2(Task* _thisptr, const char* taskId, uint32 accountId) // __ct__Q3_2nn4boss4TaskFPCcUi + static Task* ctor2(Task* _thisptr, const char* taskId, uint32 persistentId) // __ct__Q3_2nn4boss4TaskFPCcUi { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); - auto r = Initialize1(_thisptr, taskId, accountId); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); + auto r = Initialize1(_thisptr, taskId, persistentId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; } @@ -727,10 +538,10 @@ bossBufferVector->buffer = (uint8*)bossRequest; { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize2(_thisptr, slot, taskId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; @@ -740,10 +551,10 @@ bossBufferVector->buffer = (uint8*)bossRequest; { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize3(_thisptr, taskId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; @@ -753,208 +564,168 @@ bossBufferVector->buffer = (uint8*)bossRequest; { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); memset(&_thisptr->taskId, 0x00, sizeof(TaskId)); return _thisptr; } static void dtor(Task* _this, uint32 options) // __dt__Q3_2nn4boss4TaskFv { - cemuLog_logDebug(LogType::Force, "nn::boss::Task::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); // todo - Task::Finalize if(options & 1) boss_delete(_this); } - static Result Run(Task* _thisptr, bool isForegroundRun) + static BossResult Run(Task* _thisptr, bool isForegroundRun) { - if (isForegroundRun != 0) + uint8be isConnected = 0; + nn_ac::IsApplicationConnected(&isConnected); + if (isForegroundRun && !isConnected) { - cemuLog_logDebug(LogType::Force, "export_Run foreground run"); + cemuLog_logDebug(LogType::Force, "nn::boss::Task::Run: Application is not connected, returning error"); + return 0xA0223A00; } - - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_RUN; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->bool_parameter = isForegroundRun != 0; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRun)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParam<uint8>(isForegroundRun ? 1 : 0); + return serviceCall.Submit(); } - static Result StartScheduling(Task* _thisptr, uint8 executeImmediately) + static BossResult StartScheduling(Task* _thisptr, uint8 executeImmediately) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_START_SCHEDULING; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->bool_parameter = executeImmediately != 0; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskStartScheduling)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParam<uint8be>(executeImmediately); + return serviceCall.Submit(); } - static Result StopScheduling(Task* _thisptr) + static nnResult StopScheduling(Task* _thisptr) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_STOP_SCHEDULING; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskStopScheduling)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + return serviceCall.Submit(); } - static Result IsRegistered(Task* _thisptr) + static bool IsRegistered(Task* _thisptr) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_IS_REGISTERED; - bossRequest->accountId = _thisptr->accountId; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->taskId = _thisptr->taskId.id; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return bossRequest->returnCode; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskIsRegistered)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return false; + return serviceCall.ReadResponse<uint8be>() != 0; } - static Result Wait(Task* _thisptr, uint32 timeout, uint32 waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState + static bool Wait(Task* _thisptr, uint32 timeout, TaskWaitState waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_WAIT; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->timeout = timeout; - bossRequest->waitState = waitState; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return bossRequest->returnCode; + static_assert(sizeof(TaskSettingCore) == 0xC00); + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskWaitA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParam<betype<TaskWaitState>>(waitState); + serviceCall.WriteParam<uint32be>(timeout); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return false; + uint8 isNotTimeout = serviceCall.ReadResponse<uint8be>(); + return isNotTimeout; } - static Result RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting + static BossResult RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->settings = settings; - bossRequest->uk1 = 0xC00; - - if (TaskSetting::IsPrivileged(settings)) - bossRequest->titleId = _thisptr->titleId.u64; - - Result result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - return result; + static_assert(sizeof(TaskSettingCore) == 0xC00); + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRegisterForImmediateRunA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParamBuffer(settings, sizeof(TaskSettingCore)); + return serviceCall.Submit(); } - static Result Unregister(Task* _thisptr) + static BossResult Unregister(Task* _thisptr) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_UNREGISTER; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - - const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - return result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskUnregister)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + return serviceCall.Submit(); } - static Result Register(Task* _thisptr, TaskSetting* settings) + static BossResult Register(Task* _thisptr, TaskSetting* settings) { + static_assert(sizeof(TaskSettingCore) == 0xC00); if (!settings) { cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); // settings should never be zero return 0; } - - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->settings = settings; - bossRequest->uk1 = 0xC00; - - if(TaskSetting::IsPrivileged(settings)) - bossRequest->titleId = _thisptr->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return bossRequest->returnCode; + // todo - missing vcall which leads to nn::boss::PlayReportSetting::RegisterPreprocess (and other functions?) being called? + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRegisterA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParamBuffer(settings, sizeof(TaskSettingCore)); + return serviceCall.Submit(); } - static uint32 GetTurnState(Task* _this, uint32be* executionCountOut) + static TaskTurnState GetTurnState(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_TURN_STATE; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u32.exec_count; - - return bossRequest->u32.result; - // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetTurnState)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return (TaskTurnState)0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<betype<TaskTurnState>>(); } - static uint64 GetContentLength(Task* _this, uint32be* executionCountOut) + static uint64 GetContentLength(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u64.exec_count; - - return bossRequest->u64.result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetContentLength)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<uint64be>(); } - static uint64 GetProcessedLength(Task* _this, uint32be* executionCountOut) + static uint64 GetProcessedLength(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u64.exec_count; - return bossRequest->u64.result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetProcessedLength)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<uint64be>(); } - static uint32 GetHttpStatusCode(Task* _this, uint32be* executionCountOut) + static uint32 GetHttpStatusCode(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u32.exec_count; - - return bossRequest->u32.result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetHttpStatusCodeA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<uint32be>(); } static void InitVTable() @@ -1030,14 +801,11 @@ bossBufferVector->buffer = (uint8*)bossRequest; static uint32 Initialize(AlmightyTask* _thisptr, TitleId* titleId, const char* taskId, uint32 accountId) { if (!_thisptr) - return 0xc0203780; - - _thisptr->accountId = accountId; + return resultInvalidParam; + _thisptr->persistentId = accountId; _thisptr->titleId.u64 = titleId->u64; - strncpy(_thisptr->taskId.id, taskId, 8); - _thisptr->taskId.id[7] = 0x00; - - return 0x200080; + _thisptr->taskId.id.assign(taskId); + return resultSuccess; } static void InitVTable() @@ -1049,40 +817,34 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static_assert(sizeof(AlmightyTask) == 0x20); - struct DataName + DataName* DataName::ctor(DataName* _this) { - char name[32]; + if(!_this) + _this = boss_new<DataName>(); + _this->name.ClearAllBytes(); + return _this; + } - static DataName* ctor(DataName* _this) // __ct__Q3_2nn4boss8DataNameFv - { - if(!_this) - _this = boss_new<DataName>(); - memset(_this->name, 0, sizeof(name)); - return _this; - } - - static const char* operator_const_char(DataName* _this) // __opPCc__Q3_2nn4boss8DataNameCFv - { - return _this->name; - } - }; - static_assert(sizeof(DataName) == 0x20); + const char* DataName::operator_const_char(DataName* _this) + { + return _this->name.c_str(); + } struct BossStorageFadEntry { - char name[32]; - uint32be fileNameId; + CafeString<32> name; + uint32be dataId; uint32 ukn24; uint32 ukn28; uint32 ukn2C; uint32 ukn30; - uint32be timestampRelated; // guessed + uint32be timestampRelated; // unsure }; - -#define FAD_ENTRY_MAX_COUNT 512 + static_assert(sizeof(BossStorageFadEntry) == 0x38); struct Storage { + static_assert(sizeof(DirectoryName) == 8); struct VTableStorage { VTableEntry rtti; @@ -1096,10 +858,10 @@ bossBufferVector->buffer = (uint8*)bossRequest; kStorageKind_RawDl, }; - /* +0x00 */ uint32be accountId; + /* +0x00 */ uint32be persistentId; /* +0x04 */ uint32be storageKind; /* +0x08 */ uint8 ukn08Array[3]; - /* +0x0B */ char storageName[8]; + /* +0x0B */ DirectoryName storageName; uint8 ukn13; uint8 ukn14; uint8 ukn15; @@ -1120,41 +882,23 @@ bossBufferVector->buffer = (uint8*)bossRequest; static void dtor(nn::boss::Storage* _this, uint32 options) // __dt__Q3_2nn4boss7StorageFv { - cemuLog_logDebug(LogType::Force, "nn::boss::Storage::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); Finalize(_this); if(options & 1) boss_delete(_this); } - static void nnBossStorage_prepareTitleId(Storage* storage) + static BossResult Initialize(Storage* _thisptr, const char* storageName, uint32 accountId, StorageKind type) { - if (storage->titleId.u64 != 0) - return; - storage->titleId.u64 = CafeSystem::GetForegroundTitleId(); - } - - static Result Initialize(Storage* _thisptr, const char* dirName, uint32 accountId, StorageKind type) - { - if (!dirName) - return 0xC0203780; - - cemuLog_logDebug(LogType::Force, "boss::Storage::Initialize({}, 0x{:08x}, {})", dirName, accountId, type); - + if (!storageName) + return resultInvalidParam; _thisptr->storageKind = type; _thisptr->titleId.u64 = 0; - - memset(_thisptr->storageName, 0, 0x8); - strncpy(_thisptr->storageName, dirName, 0x8); - _thisptr->storageName[7] = '\0'; - - _thisptr->accountId = accountId; - - nnBossStorage_prepareTitleId(_thisptr); // usually not done like this - - return 0x200080; + _thisptr->storageName.name2.assign(storageName); + _thisptr->persistentId = accountId; + return resultSuccess; } - static Result Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) + static BossResult Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) { return Initialize(_thisptr, dirName, 0, type); } @@ -1164,110 +908,34 @@ bossBufferVector->buffer = (uint8*)bossRequest; memset(_this, 0, sizeof(Storage)); // todo - not all fields might be cleared } - static Result GetDataList(nn::boss::Storage* storage, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 + static bool Exist(nn::boss::Storage* _thisptr) { - // initialize titleId of storage if not already done - nnBossStorage_prepareTitleId(storage); - - if(startIndex >= FAD_ENTRY_MAX_COUNT) { - *outputEntryCount = 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::StorageExist)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storageKind); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) return 0; - } - - // load fad.db - BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 validEntryCount = 0; - for (sint32 i = startIndex; i < FAD_ENTRY_MAX_COUNT; i++) - { - if( fadTable[i].name[0] == '\0' ) - continue; - memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); - validEntryCount++; - if (validEntryCount >= maxEntries) - break; - } - *outputEntryCount = validEntryCount; - free(fadTable); - } - else - { - // could not load fad table - *outputEntryCount = 0; - } - return 0; // todo + return serviceCall.ReadResponse<uint8be>(); } - static bool Exist(nn::boss::Storage* storage) + static nnResult GetDataList(nn::boss::Storage* _thisptr, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 { - cemuLog_logDebug(LogType::Force, "nn_boss::Storage::Exist() TODO"); - return true; - } - - /* FAD access */ - - static FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage* storage, uint32 fileNameId) - { - char storageFilePath[1024]; - sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); - sint32 fscStatus; - FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); - return fscStorageFile; - } - - static BossStorageFadEntry* nnBossStorageFad_getTable(nn::boss::Storage* storage) - { - const auto accountId = ActiveSettings::GetPersistentId(); - char fadPath[1024]; - sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); - - sint32 fscStatus; - FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (!fscFadFile) - { - return nullptr; - } - // skip first 8 bytes - fsc_setFileSeek(fscFadFile, 8); - // read entries - BossStorageFadEntry* fadTable = (BossStorageFadEntry*)malloc(sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); - memset(fadTable, 0, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); - fsc_readFile(fscFadFile, fadTable, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); - fsc_close(fscFadFile); - return fadTable; - } - - // Find index of entry by name. Returns -1 if not found - static sint32 nnBossStorageFad_getIndexByName(BossStorageFadEntry* fadTable, char* name) - { - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) - { - if (fadTable[i].name[0] == '\0') - continue; - if (strncmp(name, fadTable[i].name, 0x20) == 0) - { - return i; - } - } - return -1; - } - - static bool nnBossStorageFad_getEntryByName(nn::boss::Storage* storage, char* name, BossStorageFadEntry* fadEntry) - { - BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); - if (entryIndex >= 0) - { - memcpy(fadEntry, fadTable + entryIndex, sizeof(BossStorageFadEntry)); - free(fadTable); - return true; - } - free(fadTable); - } - return false; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::StorageGetDataList)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storageKind); + serviceCall.WriteResponseBuffer(dataList, sizeof(DataName) * maxEntries); + serviceCall.WriteParam<uint32be>(startIndex); + nnResult r = serviceCall.Submit(); + uint32be outputCount = serviceCall.ReadResponse<uint32be>(); + bool isSuccessByte = serviceCall.ReadResponse<uint8be>() != 0; + if (!isSuccessByte) + return resultUknA0220300; + if (outputEntryCount) + *outputEntryCount = outputCount; + return r; } static void InitVTable() @@ -1294,7 +962,6 @@ bossBufferVector->buffer = (uint8*)bossRequest; static AlmightyStorage* ctor(AlmightyStorage* _thisptr) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_ctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); if (!_thisptr) _thisptr = boss_new<AlmightyStorage>(); Storage::ctor1(_thisptr); @@ -1304,18 +971,16 @@ bossBufferVector->buffer = (uint8*)bossRequest; static uint32 Initialize(AlmightyStorage* _thisptr, TitleId* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_Initialize(0x{:x})", MEMPTR(_thisptr).GetMPTR()); if (!_thisptr) - return 0xc0203780; + return resultInvalidParam; - _thisptr->accountId = accountId; + _thisptr->persistentId = accountId; _thisptr->storageKind = storageKind; _thisptr->titleId.u64 = titleId->u64; - strncpy(_thisptr->storageName, storageName, 8); - _thisptr->storageName[0x7] = 0x00; + _thisptr->storageName.name2.assign(storageName); - return 0x200080; + return resultSuccess; } static void InitVTable() @@ -1338,9 +1003,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static inline SysAllocator<VTableNsData> s_vTable; - /* +0x00 */ char name[0x20]; // DataName ? - /* +0x20 */ nn::boss::Storage storage; - /* +0x48 */ uint64 readIndex; + /* +0x00 */ DataName name; + /* +0x20 */ Storage storage; + /* +0x48 */ uint64be currentSeek; /* +0x50 */ MEMPTR<void> vTablePtr; /* +0x54 */ uint32 ukn54; @@ -1349,9 +1014,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; if (!_this) _this = boss_new<NsData>(); _this->vTablePtr = s_vTable; - memset(_this->name, 0, sizeof(_this->name)); + DataName::ctor(&_this->name); _this->storage.ctor1(&_this->storage); - _this->readIndex = 0; + _this->currentSeek = 0; return _this; } @@ -1363,42 +1028,35 @@ bossBufferVector->buffer = (uint8*)bossRequest; boss_delete(_this); } - static Result Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) + static BossResult Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) { if(dataName == nullptr) { if (storage->storageKind != 1) { - return 0xC0203780; + return resultInvalidParam; } } - _this->storage.accountId = storage->accountId; + _this->storage.persistentId = storage->persistentId; _this->storage.storageKind = storage->storageKind; - memcpy(_this->storage.ukn08Array, storage->ukn08Array, 3); - memcpy(_this->storage.storageName, storage->storageName, 8); - - _this->storage.titleId.u64 = storage->titleId.u64; - + _this->storage.storageName = storage->storageName; + _this->storage.titleId = storage->titleId; _this->storage = *storage; if (dataName != nullptr || storage->storageKind != 1) - strncpy(_this->name, dataName, 0x20); + _this->name.name.assign(dataName); else - strncpy(_this->name, "rawcontent.dat", 0x20); - _this->name[0x1F] = '\0'; + _this->name.name.assign("rawcontent.dat"); + _this->currentSeek = 0; - _this->readIndex = 0; - - cemuLog_logDebug(LogType::Force, "initialize: {}", _this->name); - - return 0x200080; + return resultSuccess; } static std::string _GetPath(NsData* nsData) { - uint32 accountId = nsData->storage.accountId; + uint32 accountId = nsData->storage.persistentId; if (accountId == 0) accountId = iosuAct_getAccountIdOfCurrentAccount(); @@ -1407,204 +1065,120 @@ bossBufferVector->buffer = (uint8*)bossRequest; title_id = CafeSystem::GetForegroundTitleId(); fs::path path = fmt::format("cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId); - path /= nsData->storage.storageName; - path /= nsData->name; + path /= nsData->storage.storageName.name2.c_str(); + path /= nsData->name.c_str(); return path.string(); } - static Result DeleteRealFileWithHistory(NsData* nsData) + static BossResult DeleteRealFile(NsData* _thisptr) { - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - // todo - cemuLog_log(LogType::Force, "BOSS NBDL: Unsupported delete"); - } - else - { - sint32 fscStatus = FSC_STATUS_OK; - std::string filePath = _GetPath(nsData).c_str(); - fsc_remove((char*)filePath.c_str(), &fscStatus); - if (fscStatus != 0) - cemuLog_log(LogType::Force, "Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); - } - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataDeleteFile)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + return serviceCall.Submit(); } - static uint32 Exist(NsData* nsData) + static BossResult DeleteRealFileWithHistory(NsData* _thisptr) { - bool fileExists = false; - if(nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - // check if name is present in fad table - BossStorageFadEntry* fadTable = nn::boss::Storage::nnBossStorageFad_getTable(&nsData->storage); - if (fadTable) - { - fileExists = nn::boss::Storage::nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; - cemuLog_logDebug(LogType::Force, "\t({}) -> {}", nsData->name, fileExists); - free(fadTable); - } - } - else - { - sint32 fscStatus; - auto fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); - if (fscStorageFile != nullptr) - { - fileExists = true; - fsc_close(fscStorageFile); - } - } - return fileExists?1:0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataDeleteFileWithHistory)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + return serviceCall.Submit(); } - static uint64 GetSize(NsData* nsData) + static uint32 Exist(NsData* _thisptr) { - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - BossStorageFadEntry fadEntry; - if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) - { - cemuLog_log(LogType::Force, "BOSS storage cant find file {}", nsData->name); - return 0; - } - // open file - fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - - if (fscStorageFile == nullptr) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {}", nsData->name); + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataExist)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) return 0; - } + return serviceCall.ReadResponse<uint8be>(); + } - // get size - const sint32 fileSize = fsc_getFileSize(fscStorageFile); - // close file - fsc_close(fscStorageFile); - return fileSize; + static uint64 GetSize(NsData* _thisptr) + { + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataGetSize)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + return serviceCall.ReadResponse<uint64be>(); } static uint64 GetCreatedTime(NsData* nsData) { cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0"); uint64 createdTime = 0; + // cmdId 0x97 + // probably uses FS stat query return createdTime; } - static uint32 nnBossNsData_read(NsData* nsData, uint64be* sizeOutBE, void* buffer, sint32 length) + static sint32 ReadWithSizeOut(NsData* _thisptr, uint64be* sizeOut, uint8* buffer, sint32 length) { - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - BossStorageFadEntry fadEntry; - if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) - { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // open file - fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - - if (!fscStorageFile) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // verify read is within bounds - sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; - sint32 readBytes = length; - if (readEndOffset > fileSize) - { - readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); - cemu_assert_debug(readBytes != 0); - } - // read - fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); - fsc_readFile(fscStorageFile, buffer, readBytes); - nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); - - // close file - fsc_close(fscStorageFile); - if (sizeOutBE) - *sizeOutBE = readBytes; - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataRead)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + serviceCall.WriteResponseBuffer(buffer, length); + uint64 readOffset = _thisptr->currentSeek; + serviceCall.WriteParam<uint64be>(readOffset); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return r; + uint64 numReadBytes = serviceCall.ReadResponse<uint64be>(); + _thisptr->currentSeek += numReadBytes; + cemu_assert(sizeOut); + cemu_assert(numReadBytes <= length); + *sizeOut = numReadBytes; + return r; } -#define NSDATA_SEEK_MODE_BEGINNING (0) - - static uint32 nnBossNsData_seek(NsData* nsData, uint64 seek, uint32 mode) + static sint32 Read(NsData* _thisptr, uint8* buffer, sint32 length) { - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - BossStorageFadEntry fadEntry; - if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) - { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // open file - fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } + uint64be bytesRead = 0; + return ReadWithSizeOut(_thisptr, &bytesRead, buffer, length); + } - if (fscStorageFile == nullptr) + enum class NsDataSeekMode + { + Beginning = 0, + Relative = 1, + Ending = 2 + }; + + static BossResult Seek(NsData* _thisptr, sint64 seekPos, NsDataSeekMode mode) + { + if (mode == NsDataSeekMode::Beginning) { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code + _thisptr->currentSeek = seekPos; } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // handle seek - if (mode == NSDATA_SEEK_MODE_BEGINNING) + else if (mode == NsDataSeekMode::Relative) { - seek = std::min(seek, (uint64)fileSize); - nsData->readIndex = _swapEndianU64((uint64)seek); + _thisptr->currentSeek += seekPos; + } + else if (mode == NsDataSeekMode::Ending) + { + uint64 fileSize = GetSize(_thisptr); + _thisptr->currentSeek = fileSize + seekPos; } else { cemu_assert_unimplemented(); } - fsc_close(fscStorageFile); - return 0; - } - - static sint32 Read(NsData* nsData, uint8* buffer, sint32 length) - { - cemuLog_logDebug(LogType::Force, "nsData read (filename {})", nsData->name); - return nnBossNsData_read(nsData, nullptr, buffer, length); - } - - static sint32 ReadWithSizeOut(NsData* nsData, uint64be* sizeOut, uint8* buffer, sint32 length) - { - uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); - cemuLog_logDebug(LogType::Force, "nsData readWithSizeOut (filename {} length 0x{:x}) Result: {} Sizeout: {:x}", nsData->name, length, r, _swapEndianU64(*sizeOut)); - return r; - } - - static Result Seek(NsData* nsData, uint64 seekPos, uint32 mode) - { - uint32 r = nnBossNsData_seek(nsData, seekPos, mode); - cemuLog_logDebug(LogType::Force, "nsData seek (filename {} seek 0x{:x}) Result: {}", nsData->name, (uint32)seekPos, r); - return r; + return resultSuccess; } static void InitVTable() @@ -1631,6 +1205,10 @@ void nnBoss_load() cafeExportRegisterFunc(nn::boss::IsInitialized, "nn_boss", "IsInitialized__Q2_2nn4bossFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Finalize, "nn_boss", "Finalize__Q2_2nn4bossFv", LogType::NN_BOSS); + // taskId + cafeExportRegisterFunc(nn::boss::TaskId::ctorDefault, "nn_boss", "__ct__Q3_2nn4boss6TaskIDFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TaskId::ctorFromString, "nn_boss", "__ct__Q3_2nn4boss6TaskIDFPCc", LogType::NN_BOSS); + // task nn::boss::Task::InitVTable(); cafeExportRegisterFunc(nn::boss::Task::ctor1, "nn_boss", "__ct__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); @@ -1695,9 +1273,9 @@ void nnBoss_load() // cafeExportMakeWrapper<nn::boss::Title::SetNewArrivalFlagOff>("nn_boss", "SetNewArrivalFlagOff__Q3_2nn4boss5TitleFv"); SMM bookmarks // TitleId - cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::TitleId::ctor3, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctorDefault, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctorFromTitleId, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctorCopy, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); // DataName @@ -1739,6 +1317,7 @@ void nnBoss_load() cafeExportRegisterFunc(nn::boss::NsData::ctor, "nn_boss", "__ct__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::dtor, "nn_boss", "__dt__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Initialize, "nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFile, "nn_boss", "DeleteRealFile__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFileWithHistory, "nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Exist, "nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::GetSize, "nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); diff --git a/src/Cafe/OS/libs/nn_client_service.h b/src/Cafe/OS/libs/nn_client_service.h new file mode 100644 index 00000000..fa4ed5cd --- /dev/null +++ b/src/Cafe/OS/libs/nn_client_service.h @@ -0,0 +1,261 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include <boost/container/static_vector.hpp> + +class IPCServiceClient +{ +public: + class IPCServiceCall + { + struct IOVectorBuffer + { + IOVectorBuffer() = default; + IOVectorBuffer(uint8* ptr, uint32 size, bool isExternalBuffer = false) : ptr(ptr), size(size), isExternalBuffer(isExternalBuffer) {} + + uint8* ptr; + uint32 size; + bool isExternalBuffer{false}; // buffer provided by caller + }; + public: + IPCServiceCall(IPCServiceClient& client, uint32 serviceId, uint32 commandId) : m_client(client) + { + // allocate a parameter and response buffer + IPCBuffer* cmdBuf = client.AllocateCommandBuffer(); + m_paramBuffers.emplace_back(cmdBuf->data, sizeof(IPCBuffer)); + cmdBuf = client.AllocateCommandBuffer(); + m_responseBuffers.emplace_back(cmdBuf->data, sizeof(IPCBuffer)); + // write the request header + WriteParam<uint32be>(0); // ukn00 + WriteParam<uint32be>(serviceId); // serviceId + WriteParam<uint32be>(0); // ukn08 + WriteParam<uint32be>(commandId); // commandId + } + + ~IPCServiceCall() + { + for (auto& buf : m_paramBuffers) + { + if (buf.isExternalBuffer) + continue; + m_client.ReleaseCommandBuffer((IPCBuffer*)buf.ptr); + } + for (auto& buf : m_responseBuffers) + { + if (buf.isExternalBuffer) + continue; + m_client.ReleaseCommandBuffer((IPCBuffer*)buf.ptr); + } + } + + IPCServiceCall(const IPCServiceCall&) = delete; + IPCServiceCall& operator=(const IPCServiceCall&) = delete; + + template<typename T> + void WriteParam(const T& value) + { + cemu_assert(m_paramWriteIndex + sizeof(T) <= m_paramBuffers[0].size); + memcpy(m_paramBuffers[0].ptr + m_paramWriteIndex, &value, sizeof(T)); + m_paramWriteIndex += sizeof(T); + } + + // ptr and size defines an input buffer (PPC->IOSU) + void WriteParamBuffer(MEMPTR<void> ptr, uint32 size) + { + WriteInOutBuffer(MEMPTR<uint8>(ptr), size, false); + } + + // ptr and size defines an output buffer (IOSU->PPC) + void WriteResponseBuffer(MEMPTR<void> ptr, uint32 size) + { + WriteInOutBuffer(MEMPTR<uint8>(ptr), size, true); + } + + nnResult Submit() + { + StackAllocator<IPCIoctlVector, 16> vectorArray; + uint32 ioVecCount = m_paramBuffers.size() + m_responseBuffers.size(); + cemu_assert(ioVecCount <= 16); + // output buffers come first + for (size_t i = 0; i < m_responseBuffers.size(); ++i) + { + vectorArray[i].baseVirt = MEMPTR<uint8>(m_responseBuffers[i].ptr); + vectorArray[i].basePhys = nullptr; + vectorArray[i].size = m_responseBuffers[i].size; + } + // input buffers + for (size_t i = 0; i < m_paramBuffers.size(); ++i) + { + vectorArray[m_responseBuffers.size() + i].baseVirt = MEMPTR<uint8>(m_paramBuffers[i].ptr); + vectorArray[m_responseBuffers.size() + i].basePhys = nullptr; + vectorArray[m_responseBuffers.size() + i].size = m_paramBuffers[i].size; + } + IOS_ERROR r = coreinit::IOS_Ioctlv(m_client.GetDevHandle(), 0, m_responseBuffers.size(), m_paramBuffers.size(), vectorArray.GetPointer()); + if ( (r&0x80000000) != 0) + { + cemu_assert_unimplemented(); // todo - handle submission errors + } + uint32be resultCode = ReadResponse<uint32be>(); + if (!NN_RESULT_IS_FAILURE((uint32)resultCode)) + { + for (auto& bufCopy : m_bufferCopiesOut) + { + memcpy(bufCopy.dst, bufCopy.src, bufCopy.size); + } + } + return static_cast<nnResult>(resultCode); + } + + template<typename T> + T ReadResponse() + { + cemu_assert(m_responseReadIndex + sizeof(T) <= m_responseBuffers[0].size); + T value; + memcpy(&value, m_responseBuffers[0].ptr + m_responseReadIndex, sizeof(T)); + m_responseReadIndex += sizeof(T); + return value; + } + + private: + struct BufferCopyOut + { + BufferCopyOut(void* dst, void* src, uint32 size) : dst(dst), src(src), size(size) {} + + void* dst; + void* src; + uint32 size; + }; + + void WriteInOutBuffer(MEMPTR<uint8> ptr, uint32 size, bool isOutput) + { + uint32 headSize = (0x40 - (ptr.GetMPTR()&0x3F))&0x3F; + headSize = std::min<uint32>(headSize, size); + uint32 alignedSize = size - headSize; + uint32 tailSize = alignedSize - (alignedSize&~0x3F); + alignedSize -= tailSize; + // verify + cemu_assert_debug(headSize + alignedSize + tailSize == size); + cemu_assert_debug(alignedSize == 0 || ((ptr.GetMPTR()+headSize)&0x3F) == 0); + cemu_assert_debug(tailSize == 0 || ((ptr.GetMPTR()+headSize+alignedSize)&0x3F) == 0); + + if (isOutput) + cemu_assert(m_responseBuffers.size()+2 <= m_responseBuffers.capacity()); + else + cemu_assert(m_paramBuffers.size()+2 <= m_paramBuffers.capacity()); + IOVectorBuffer alignedBuffer; + alignedBuffer.ptr = ptr + headSize; + alignedBuffer.size = alignedSize; + alignedBuffer.isExternalBuffer = true; + if (isOutput) + m_responseBuffers.emplace_back(alignedBuffer); + else + m_paramBuffers.emplace_back(alignedBuffer); + IPCBuffer* headAndTailBuffer = m_client.AllocateCommandBuffer(); + IOVectorBuffer headAndTail; + headAndTail.ptr = headAndTailBuffer->data + (0x40 - headSize); + headAndTail.size = 128 - (0x40 - headSize); + headAndTail.isExternalBuffer = false; + if (headSize > 0) + { + if (isOutput) + m_bufferCopiesOut.emplace_back(ptr, headAndTailBuffer->data + 0x40 - headSize, headSize); + else + memcpy(headAndTailBuffer->data + 0x40 - headSize, ptr, headSize); + } + if (tailSize > 0) + { + if (isOutput) + m_bufferCopiesOut.emplace_back(ptr + headSize + alignedSize, headAndTailBuffer->data + 0x40, tailSize); + else + memcpy(headAndTailBuffer->data + 0x40, ptr + headSize + alignedSize, tailSize); + } + if (isOutput) + m_responseBuffers.emplace_back(headAndTail); + else + m_paramBuffers.emplace_back(headAndTail); + // serialize into parameter stream + WriteParam<uint32be>(alignedSize); + WriteParam<uint8be>(0); // ukn4 + WriteParam<uint8be>(0); // ukn5 + WriteParam<uint8be>((uint8)headSize); + WriteParam<uint8be>((uint8)tailSize); + } + + IPCServiceClient& m_client; + boost::container::static_vector<IOVectorBuffer, 8> m_paramBuffers; + boost::container::static_vector<IOVectorBuffer, 8> m_responseBuffers; + sint32 m_paramWriteIndex{0}; + sint32 m_responseReadIndex{0}; + boost::container::static_vector<BufferCopyOut, 16> m_bufferCopiesOut; + }; + + IPCServiceClient() + { + } + + ~IPCServiceClient() + { + Shutdown(); + } + + void Initialize(std::string_view devicePath, uint8_t* buffer, uint32_t bufferSize) + { + m_devicePath = devicePath; + m_buffer = buffer; + m_bufferSize = bufferSize; + + static_assert(sizeof(IPCBuffer) == 256); + size_t numCommandBuffers = m_bufferSize / sizeof(IPCBuffer); + + m_commandBuffersFree.resize(numCommandBuffers); + for (size_t i = 0; i < numCommandBuffers; ++i) + { + m_commandBuffersFree[i] = reinterpret_cast<IPCBuffer*>(m_buffer + i * sizeof(IPCBuffer)); + } + m_clientHandle = coreinit::IOS_Open(m_devicePath.c_str(), 0); + } + + void Shutdown() + { + if (m_clientHandle != 0) + { + coreinit::IOS_Close(m_clientHandle); + m_clientHandle = 0; + } + } + + IPCServiceCall Begin(uint32_t serviceId, uint32_t commandId) + { + return IPCServiceCall(*this, serviceId, commandId); + } + + IOSDevHandle GetDevHandle() const + { + cemu_assert(m_clientHandle != 0); + return m_clientHandle; + } +private: + struct IPCBuffer + { + uint8 data[256]; + }; + + IPCBuffer* AllocateCommandBuffer() + { + cemu_assert(m_commandBuffersFree.size() > 0); + IPCBuffer* buf = m_commandBuffersFree.back(); + m_commandBuffersFree.pop_back(); + return buf; + } + + void ReleaseCommandBuffer(IPCBuffer* buffer) + { + m_commandBuffersFree.emplace_back(buffer); + } + +private: + std::string m_devicePath; + IOSDevHandle m_clientHandle{0}; + uint8_t* m_buffer{nullptr}; + uint32_t m_bufferSize{0}; + std::vector<IPCBuffer*> m_commandBuffersFree; +}; diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index b5dd0e0f..e105c145 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -14,7 +14,7 @@ namespace nsyshid::backend::libusb { m_ctx = nullptr; cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}", - m_initReturnCode); + m_initReturnCode); return; } @@ -33,8 +33,8 @@ namespace nsyshid::backend::libusb if (ret != LIBUSB_SUCCESS) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}", - ret); + "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}", + ret); } else { @@ -51,8 +51,8 @@ namespace nsyshid::backend::libusb if (ret != 0) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", - ret); + "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", + ret); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } @@ -137,8 +137,8 @@ namespace nsyshid::backend::libusb case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}", - desc.idVendor, - desc.idProduct); + desc.idVendor, + desc.idProduct); auto device = CheckAndCreateDevice(dev); if (device != nullptr) { @@ -165,8 +165,8 @@ namespace nsyshid::backend::libusb case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}", - desc.idVendor, - desc.idProduct); + desc.idVendor, + desc.idProduct); auto device = FindLibusbDevice(dev); if (device != nullptr) { @@ -202,7 +202,7 @@ namespace nsyshid::backend::libusb if (ret < 0) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); + "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); return nullptr; } uint8 busNumber = libusb_get_bus_number(dev); @@ -267,7 +267,7 @@ namespace nsyshid::backend::libusb if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); + "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, @@ -492,7 +492,7 @@ namespace nsyshid::backend::libusb if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); + "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); return ReadResult::Error; } @@ -525,8 +525,8 @@ namespace nsyshid::backend::libusb return ReadResult::Success; } cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn, - libusb_error_name(ret)); + "nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn, + libusb_error_name(ret)); return ReadResult::Error; } @@ -536,7 +536,7 @@ namespace nsyshid::backend::libusb if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); + "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); return WriteResult::Error; } @@ -565,8 +565,8 @@ namespace nsyshid::backend::libusb return WriteResult::Success; } cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::write(): failed with error code: {}", - ret); + "nsyshid::DeviceLibusb::write(): failed with error code: {}", + ret); return WriteResult::Error; } @@ -763,6 +763,9 @@ namespace nsyshid::backend::libusb int DeviceLibusb::ClaimAllInterfaces(uint8 config_num) { const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + // On macos detaching would fail without root or entitlement. + // We assume user is using GCAdapterDriver and therefore don't want to detach anything +#if !defined(__APPLE__) if (libusb_kernel_driver_active(this->m_libusbHandle, i)) { const int ret2 = libusb_detach_kernel_driver(this->m_libusbHandle, i); @@ -773,6 +776,7 @@ namespace nsyshid::backend::libusb return ret2; } } +#endif return libusb_claim_interface(this->m_libusbHandle, i); }); if (ret < LIBUSB_SUCCESS) diff --git a/src/Cemu/CMakeLists.txt b/src/Cemu/CMakeLists.txt index cdf71c62..83ab0af6 100644 --- a/src/Cemu/CMakeLists.txt +++ b/src/Cemu/CMakeLists.txt @@ -43,17 +43,8 @@ set_property(TARGET CemuComponents PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$ target_include_directories(CemuComponents PUBLIC "../") target_link_libraries(CemuComponents PRIVATE - CemuCafe CemuCommon - CemuConfig - CemuGui - CemuUtil - Boost::headers - CURL::libcurl - OpenSSL::Crypto - OpenSSL::SSL - pugixml::pugixml - ZLIB::ZLIB + CemuGui ) # PUBLIC because fmt/format.h is included in ExpressionParser/ExpressionParser.h diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 811ac6b4..f2d9ac05 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -224,6 +224,24 @@ bool cemuLog_log(LogType type, std::u8string_view text) return cemuLog_log(type, s); } +void cemuLog_logHexDump(LogType type, const void* data, size_t size, size_t lineSize) +{ + const uint8* dataU8 = static_cast<const uint8*>(data); + std::vector<char> hexLine; + hexLine.resize(6 + lineSize * 3 + 1, ' '); + for (size_t i = 0; i < size; i += lineSize) + { + sprintf(hexLine.data(), "%04X: ", (int)i); + size_t remainingSize = std::min<size_t>(lineSize, size - i); + for (size_t j=0; j<remainingSize; j++) + sprintf(hexLine.data() + 6 + j * 3, "%02X ", dataU8[j]); + dataU8 += remainingSize; + if (remainingSize == lineSize) + hexLine.resize(6 + remainingSize * 3 + 1); + cemuLog_log(type, hexLine.data()); + } +} + void cemuLog_waitForFlush() { cemuLog_createLogFile(false); diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 39b27e30..25ec6ad8 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -125,6 +125,9 @@ bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) #define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } } +// utility function for logging binary data as a hex dump +void cemuLog_logHexDump(LogType type, const void* data, size_t size, size_t lineSize = 16); + // cafe lib calls bool cemuLog_advancedPPCLoggingEnabled(); diff --git a/src/Cemu/PPCAssembler/ppcAssembler.cpp b/src/Cemu/PPCAssembler/ppcAssembler.cpp index df20b21d..878d5f47 100644 --- a/src/Cemu/PPCAssembler/ppcAssembler.cpp +++ b/src/Cemu/PPCAssembler/ppcAssembler.cpp @@ -269,6 +269,7 @@ const char* ppcAssembler_getInstructionName(uint32 ppcAsmOp) case PPCASM_OP_PS_NMSUB: return "PS_NMSUB"; case PPCASM_OP_FMR: return "FMR"; + case PPCASM_OP_FABS: return "FABS"; case PPCASM_OP_FNEG: return "FNEG"; case PPCASM_OP_FRSP: return "FRSP"; case PPCASM_OP_FRSQRTE: return "FRSQRTE"; @@ -1325,6 +1326,7 @@ PPCInstructionDef ppcInstructionTable[] = {PPCASM_OP_FCMPO, 0, 63, 32|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_X_FP_CMP, FLG_DEFAULT, 0, 0, nullptr }, {PPCASM_OP_FNEG, 0, 63, 40|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMR, 0, 63, 72|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FABS, 0, 63, 264|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, }; @@ -3194,6 +3196,13 @@ void ppcAsmTestDisassembler() checkOpFPR(0, 7); checkOpFPR(1, 12); + // FABS + _testAsm(0xFC200A10, "fabs f1, f1"); + disassemble(0xFC200A10, PPCASM_OP_FABS); + checkOperandMask(true, true); + checkOpFPR(0, 1); + checkOpFPR(1, 1); + // FCTIWZ _testAsm(0xFD80401E, "fctiwz f12, f8"); disassemble(0xFD80401E, PPCASM_OP_FCTIWZ); diff --git a/src/Cemu/PPCAssembler/ppcAssembler.h b/src/Cemu/PPCAssembler/ppcAssembler.h index c47e7efd..6fb4fcef 100644 --- a/src/Cemu/PPCAssembler/ppcAssembler.h +++ b/src/Cemu/PPCAssembler/ppcAssembler.h @@ -233,6 +233,7 @@ enum PPCASM_OP // FP PPCASM_OP_FMR, + PPCASM_OP_FABS, PPCASM_OP_FNEG, PPCASM_OP_FRSP, PPCASM_OP_FRSQRTE, diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index 182c5371..ce11e2fe 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -23,7 +23,7 @@ CURLcode _sslctx_function_NUS(CURL* curl, void* sslctx, void* param) { cemuLog_log(LogType::Force, "Invalid CA certificate (102)"); } - if (iosuCrypto_addCACertificate(sslctx, 0x69) == false) + if (iosuCrypto_addCACertificate(sslctx, 105) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); } @@ -79,7 +79,6 @@ CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param) cemuLog_log(LogType::Force, "Olive client certificate error"); } - // NSSLAddServerPKIGroups(sslCtx, 3, &x, &y); { std::vector<sint16> certGroups = { 100, 101, 102, 103, 104, 105, @@ -99,6 +98,32 @@ CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param) return CURLE_OK; } +CURLcode _sslctx_function_CUSTOM(CURL* curl, void* sslctx, void* param) +{ + CurlRequestHelper* requestHelper = (CurlRequestHelper*)param; + for (auto& caCertId : requestHelper->GetCaCertIds()) + { + if (iosuCrypto_addCACertificate(sslctx, caCertId) == false) + { + cemuLog_log(LogType::Force, "Invalid CA certificate ({})", caCertId); + } + } + for (auto& clientCertId : requestHelper->GetClientCertIds()) + { + if (iosuCrypto_addCACertificate(sslctx, clientCertId) == false) + { + cemuLog_log(LogType::Force, "Invalid client certificate ({})", clientCertId); + } + } + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + if (requestHelper->GetClientCertIds().empty()) + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_NONE, nullptr); + else + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); + return CURLE_OK; +} + CurlRequestHelper::CurlRequestHelper() { m_curl = curl_easy_init(); @@ -162,6 +187,11 @@ void CurlRequestHelper::initate(NetworkService service, std::string url, SERVER_ curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_OLIVE); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } + else if (sslContext == SERVER_SSL_CONTEXT::CUSTOM) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_CUSTOM); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, this); + } else { cemu_assert(false); @@ -218,7 +248,8 @@ bool CurlRequestHelper::submitRequest(bool isPost) // post if (isPost) { - if (!m_isUsingMultipartFormData) { + if (!m_isUsingMultipartFormData) + { curl_easy_setopt(m_curl, CURLOPT_POST, 1); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); @@ -242,9 +273,14 @@ bool CurlRequestHelper::submitRequest(bool isPost) // check response code long httpCode = 0; curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode); + m_httpStatusCode = httpCode; if (httpCode != 200) { cemuLog_log(LogType::Force, "HTTP request received response {} but expected 200", httpCode); + char* effectiveUrl = nullptr; + curl_easy_getinfo(m_curl, CURLINFO_EFFECTIVE_URL, &effectiveUrl); + if (effectiveUrl) + cemuLog_log(LogType::Force, "Request: {}", effectiveUrl); // error status codes (4xx or 5xx range) are always considered a failed request, except for code 400 which is usually returned as a response to failed logins etc. if (httpCode >= 400 && httpCode <= 599 && httpCode != 400) return false; diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h index adfe7393..bc01711b 100644 --- a/src/Cemu/napi/napi_helper.h +++ b/src/Cemu/napi/napi_helper.h @@ -28,6 +28,7 @@ public: IDBE, // idbe-wup. TAGAYA, // tagaya.wup.shop.nintendo.net OLIVE, // olv. + CUSTOM, // use cert parameters }; CurlRequestHelper(); @@ -56,6 +57,41 @@ public: m_isUsingMultipartFormData = isUsingMultipartFormData; } + void ClearCaCertIds() + { + m_caCertIds.clear(); + } + + void ClearClientCertIds() + { + m_clientCertIds.clear(); + } + + void AddCaCertId(sint32 caCertId) + { + m_caCertIds.emplace_back(caCertId); + } + + void AddClientCertId(sint32 clientCertId) + { + m_clientCertIds.emplace_back(clientCertId); + } + + std::vector<sint32> GetCaCertIds() const + { + return m_caCertIds; + } + + std::vector<sint32> GetClientCertIds() const + { + return m_clientCertIds; + } + + sint32 GetHTTPStatusCode() const + { + return m_httpStatusCode; + } + private: static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); @@ -69,6 +105,11 @@ private: void* m_writeCallbackUserData{}; bool m_isUsingMultipartFormData = false; + // cert data + std::vector<sint32> m_caCertIds; + std::vector<sint32> m_clientCertIds; + // result + sint32 m_httpStatusCode{ 0 }; }; class CurlSOAPHelper // todo - make this use CurlRequestHelper diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index dec0bc42..971dfd80 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -59,9 +59,6 @@ target_precompile_headers(CemuCommon PUBLIC precompiled.h) target_include_directories(CemuCommon PUBLIC "../") target_link_libraries(CemuCommon PRIVATE - CemuCafe - CemuConfig - CemuComponents Boost::nowide Boost::filesystem glm::glm diff --git a/src/Common/CafeString.h b/src/Common/CafeString.h index 57fc72da..7a851f68 100644 --- a/src/Common/CafeString.h +++ b/src/Common/CafeString.h @@ -7,12 +7,23 @@ template <size_t N> class CafeString // fixed buffer size, null-terminated, PPC char { public: + constexpr static size_t Size() + { + return N; + } + + // checks whether the string and a null terminator can fit into the buffer + bool CanHoldString(std::string_view sv) const + { + return sv.size() < N; + } + bool assign(std::string_view sv) { - if (sv.size()+1 >= N) + if (sv.size() >= N) { - memcpy(data, sv.data(), sv.size()-1); - data[sv.size()-1] = '\0'; + memcpy(data, sv.data(), N-1); + data[N-1] = '\0'; return false; } memcpy(data, sv.data(), sv.size()); @@ -20,11 +31,50 @@ class CafeString // fixed buffer size, null-terminated, PPC char return true; } - const char* c_str() + void Copy(CafeString<N>& other) + { + memcpy(data, other.data, N); + } + + bool empty() const + { + return data[0] == '\0'; + } + + const char* c_str() const { return (const char*)data; } + void ClearAllBytes() + { + memset(data, 0, N); + } + + auto operator<=>(const CafeString<N>& other) const + { + for (size_t i = 0; i < N; i++) + { + if (data[i] != other.data[i]) + return data[i] <=> other.data[i]; + if (data[i] == '\0') + return std::strong_ordering::equal; + } + return std::strong_ordering::equal; + } + + bool operator==(const CafeString<N>& other) const + { + for (size_t i = 0; i < N; i++) + { + if (data[i] != other.data[i]) + return false; + if (data[i] == '\0') + return true; + } + return true; + } + uint8be data[N]; }; diff --git a/src/Common/GLInclude/glFunctions.h b/src/Common/GLInclude/glFunctions.h index 412816af..9c93637b 100644 --- a/src/Common/GLInclude/glFunctions.h +++ b/src/Common/GLInclude/glFunctions.h @@ -116,6 +116,7 @@ GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1i) GLFUNC(PFNGLPROGRAMUNIFORM2IPROC, glProgramUniform2i) GLFUNC(PFNGLPROGRAMUNIFORM1IVPROC, glProgramUniform1iv) GLFUNC(PFNGLPROGRAMUNIFORM4IVPROC, glProgramUniform4iv) +GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1f) GLFUNC(PFNGLPROGRAMUNIFORM1FVPROC, glProgramUniform1fv) GLFUNC(PFNGLPROGRAMUNIFORM2FVPROC, glProgramUniform2fv) diff --git a/src/Common/SysAllocator.h b/src/Common/SysAllocator.h index 80afc129..4d59d7c6 100644 --- a/src/Common/SysAllocator.h +++ b/src/Common/SysAllocator.h @@ -1,5 +1,9 @@ #pragma once +#include <type_traits> +#include <cstddef> +#include <vector> + uint32 coreinit_allocFromSysArea(uint32 size, uint32 alignment); class SysAllocatorBase; diff --git a/src/Common/cpu_features.h b/src/Common/cpu_features.h index 130b51a8..f6c06130 100644 --- a/src/Common/cpu_features.h +++ b/src/Common/cpu_features.h @@ -1,4 +1,3 @@ - #ifdef __GNUC__ #define ATTRIBUTE_AVX2 __attribute__((target("avx2"))) #define ATTRIBUTE_SSE41 __attribute__((target("sse4.1"))) @@ -9,6 +8,8 @@ #define ATTRIBUTE_AESNI #endif +#include <string> + class CPUFeaturesImpl { public: @@ -33,4 +34,4 @@ private: char m_cpuBrandName[0x40]{ 0 }; }; -extern CPUFeaturesImpl g_CPUFeatures; \ No newline at end of file +extern CPUFeaturesImpl g_CPUFeatures; diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index a1d1a1d9..8d01499e 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -31,11 +31,8 @@ endif() target_include_directories(CemuAudio PUBLIC "../") target_link_libraries(CemuAudio PRIVATE - CemuCafe - CemuGui CemuCommon - CemuConfig - CemuUtil + CemuGui ) if(ENABLE_CUBEB) diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 58a83a15..3ecd8855 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -6,6 +6,7 @@ #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "util/helpers/helpers.h" +#include "Cafe/HW/Latte/Core/Latte.h" void ActiveSettings::SetPaths(bool isPortableMode, const fs::path& executablePath, @@ -112,6 +113,18 @@ GraphicAPI ActiveSettings::GetGraphicsAPI() return api; } +float ActiveSettings::GetTVGamma() +{ + const auto& config = GetConfig(); + return config.overrideGammaValue.GetValue() + LatteGPUState.tvGamma * !config.overrideAppGammaPreference.GetValue(); +} + +float ActiveSettings::GetDRCGamma() +{ + const auto& config = GetConfig(); + return config.overrideGammaValue.GetValue() + LatteGPUState.drcGamma * !config.overrideAppGammaPreference.GetValue(); +} + bool ActiveSettings::AudioOutputOnlyAux() { return s_audio_aux_only; diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index c01170e2..91555bb4 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -96,6 +96,10 @@ public: [[nodiscard]] static bool WaitForGX2DrawDoneEnabled(); [[nodiscard]] static GraphicAPI GetGraphicsAPI(); + // gamma + [[nodiscard]] static float GetTVGamma(); + [[nodiscard]] static float GetDRCGamma(); + // audio [[nodiscard]] static bool AudioOutputOnlyAux(); static void EnableAudioOnlyAux(bool state); diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt index abcacc60..183f124a 100644 --- a/src/config/CMakeLists.txt +++ b/src/config/CMakeLists.txt @@ -16,11 +16,7 @@ set_property(TARGET CemuConfig PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<C target_include_directories(CemuConfig PUBLIC "../") target_link_libraries(CemuConfig PRIVATE - CemuCafe CemuCommon CemuGui - CemuUtil - Boost::headers - Boost::program_options - pugixml::pugixml + Boost::program_options ) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index dcf136eb..30e2b8d1 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -130,6 +130,13 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser) graphic.get("vkDevice", vk_graphic_device_uuid); mtl_graphic_device_uuid = graphic.get("mtlDevice", 0); vsync = graphic.get("VSync", 0); + overrideAppGammaPreference = graphic.get("OverrideAppGammaPreference", false); + overrideGammaValue = graphic.get("OverrideGammaValue", 2.2f); + if(overrideGammaValue < 0) + overrideGammaValue = 2.2f; + userDisplayGamma = graphic.get("UserDisplayGamma", 2.2f); + if(userDisplayGamma < 0) + userDisplayGamma = 2.2f; gx2drawdone_sync = graphic.get("GX2DrawdoneSync", true); upscale_filter = graphic.get("UpscaleFilter", kBicubicHermiteFilter); downscale_filter = graphic.get("DownscaleFilter", kLinearFilter); @@ -351,6 +358,9 @@ XMLConfigParser CemuConfig::Save(XMLConfigParser& parser) graphic.set("vkDevice", vk_graphic_device_uuid); graphic.set("mtlDevice", mtl_graphic_device_uuid); graphic.set("VSync", vsync); + graphic.set("OverrideAppGammaPreference", overrideAppGammaPreference); + graphic.set("OverrideGammaValue", overrideGammaValue); + graphic.set("UserDisplayGamma", userDisplayGamma); graphic.set("GX2DrawdoneSync", gx2drawdone_sync); graphic.set("ForceMeshShaders", force_mesh_shaders); //graphic.set("PrecompiledShaders", precompiled_shaders.GetValue()); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 0cdd642e..cc0bad12 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -440,6 +440,11 @@ struct CemuConfig ConfigValue<bool> async_compile{ true }; ConfigValue<bool> force_mesh_shaders{ false }; + // Gamma + ConfigValue<bool> overrideAppGammaPreference{ false }; + ConfigValue<float> overrideGammaValue{ 2.2f }; + ConfigValue<float> userDisplayGamma { 2.2f }; // 0 = sRGB, >0 gamma + ConfigValue<bool> vk_accurate_barriers{ true }; struct diff --git a/src/config/ConfigValue.h b/src/config/ConfigValue.h index ecffc119..7e50d63e 100644 --- a/src/config/ConfigValue.h +++ b/src/config/ConfigValue.h @@ -4,6 +4,9 @@ #include <mutex> #include <atomic> +#include <shared_mutex> +#include <cassert> +#include <string> template<typename TType> class ConfigValueAtomic diff --git a/src/gui/wxgui/AudioDebuggerWindow.cpp b/src/gui/wxgui/AudioDebuggerWindow.cpp index d570e84b..ef1122a2 100644 --- a/src/gui/wxgui/AudioDebuggerWindow.cpp +++ b/src/gui/wxgui/AudioDebuggerWindow.cpp @@ -124,7 +124,7 @@ AudioDebuggerWindow::AudioDebuggerWindow(wxFrame& parent) sizer->Add(voiceListbox, 1, wxEXPAND | wxBOTTOM, 0); - voiceListbox->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(AudioDebuggerWindow::OnVoiceListRightClick), NULL, this); + voiceListbox->Bind(wxEVT_RIGHT_DOWN, &AudioDebuggerWindow::OnVoiceListRightClick, this); mainPane->SetSizer(sizer); diff --git a/src/gui/wxgui/CMakeLists.txt b/src/gui/wxgui/CMakeLists.txt index ff66e3d2..7db02c20 100644 --- a/src/gui/wxgui/CMakeLists.txt +++ b/src/gui/wxgui/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(CemuWxGui +add_library(CemuWxGui STATIC canvas/IRenderCanvas.h canvas/OpenGLCanvas.cpp canvas/OpenGLCanvas.h @@ -76,8 +76,6 @@ add_library(CemuWxGui input/InputAPIAddWindow.h input/InputSettings2.cpp input/InputSettings2.h - input/PairingDialog.cpp - input/PairingDialog.h input/panels/ClassicControllerInputPanel.cpp input/panels/ClassicControllerInputPanel.h input/panels/InputPanel.cpp @@ -118,11 +116,18 @@ add_library(CemuWxGui wxHelper.h ) -if(ENABLE_METAL) - target_sources(CemuWxGui PRIVATE - canvas/MetalCanvas.cpp - canvas/MetalCanvas.h - ) +if (ENABLE_METAL) + target_sources(CemuWxGui PRIVATE + canvas/MetalCanvas.cpp + canvas/MetalCanvas.h + ) +endif() + +if (ENABLE_BLUEZ) + target_sources(CemuWxGui PRIVATE + input/PairingDialog.cpp + input/PairingDialog.h + ) endif() set_property(TARGET CemuWxGui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") @@ -133,20 +138,16 @@ target_include_directories(CemuWxGui PUBLIC "../") target_include_directories(CemuWxGui PUBLIC ${RAPIDJSON_INCLUDE_DIRS}) target_link_libraries(CemuWxGui PRIVATE - CemuAudio - CemuCafe - CemuCommon - CemuComponents - CemuConfig - CemuInput - CemuResource - CemuUtil - Boost::headers - CURL::libcurl - libzip::zip - OpenSSL::Crypto - pugixml::pugixml - ZArchive::zarchive + CemuCommon + CemuResource + libzip::zip + ZArchive::zarchive + CemuComponents + SDL2::SDL2 + pugixml::pugixml + CemuCafe + PUBLIC + CURL::libcurl ) if(ENABLE_WXWIDGETS AND UNIX AND NOT APPLE) @@ -168,7 +169,7 @@ if(UNIX AND NOT APPLE) endif() if (ENABLE_WXWIDGETS) # PUBLIC because wx/app.h is included in CemuApp.h - target_link_libraries(CemuWxGui PUBLIC wx::base wx::core wx::gl wx::propgrid wx::xrc) + target_link_libraries(CemuWxGui PUBLIC wxWidgets::wxWidgets) endif() if(WIN32) diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index 1df8611a..7e6de8ec 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -63,7 +63,7 @@ void unused_translation_dummy() } #if BOOST_OS_WINDOWS -#include <shlobj_core.h> +#include <shlobj.h> fs::path GetAppDataRoamingPath() { PWSTR path = nullptr; @@ -295,6 +295,10 @@ bool CemuApp::OnInit() { MSWEnableDarkMode(DarkMode_Always); } + + // extend tooltip duration to the maximum possible value + wxToolTip::SetDelay(-1); + wxToolTip::SetAutoPop(MAKELPARAM(std::numeric_limits<short>::max(),0)); #endif for (auto&& path : failedWriteAccess) diff --git a/src/gui/wxgui/CemuUpdateWindow.cpp b/src/gui/wxgui/CemuUpdateWindow.cpp index 542ae588..fd51467d 100644 --- a/src/gui/wxgui/CemuUpdateWindow.cpp +++ b/src/gui/wxgui/CemuUpdateWindow.cpp @@ -590,16 +590,16 @@ void CemuUpdateWindow::OnClose(wxCloseEvent& event) if (m_restartRequired && !m_restartFile.empty() && fs::exists(m_restartFile)) { PROCESS_INFORMATION pi{}; - STARTUPINFO si{}; + STARTUPINFOW si{}; si.cb = sizeof(si); std::wstring cmdline = GetCommandLineW(); const auto index = cmdline.find('"', 1); cemu_assert_debug(index != std::wstring::npos); - cmdline = L"\"" + m_restartFile.wstring() + L"\"" + cmdline.substr(index + 1); + cmdline = L"\"" + boost::nowide::widen(_pathToUtf8(m_restartFile)) + L"\"" + cmdline.substr(index + 1); - HANDLE lock = CreateMutex(nullptr, TRUE, L"Global\\cemu_update_lock"); - CreateProcess(nullptr, (wchar_t*)cmdline.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); + HANDLE lock = CreateMutexW(nullptr, TRUE, L"Global\\cemu_update_lock"); + CreateProcessW(nullptr, (wchar_t*)cmdline.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); exit(0); } diff --git a/src/gui/wxgui/DownloadGraphicPacksWindow.cpp b/src/gui/wxgui/DownloadGraphicPacksWindow.cpp index c5de8d89..4a854b38 100644 --- a/src/gui/wxgui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/wxgui/DownloadGraphicPacksWindow.cpp @@ -28,7 +28,7 @@ size_t DownloadGraphicPacksWindow::curlDownloadFile_writeData(void *ptr, size_t return writeSize; } -int DownloadGraphicPacksWindow::progress_callback(curlDownloadFileState_t* downloadState, double dltotal, double dlnow, double ultotal, double ulnow) +int DownloadGraphicPacksWindow::progress_callback(curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { if (dltotal > 1.0) downloadState->progress = dlnow / dltotal; @@ -47,7 +47,7 @@ bool DownloadGraphicPacksWindow::curlDownloadFile(const char *url, curlDownloadF curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDownloadFile_writeData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadState); - curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); diff --git a/src/gui/wxgui/DownloadGraphicPacksWindow.h b/src/gui/wxgui/DownloadGraphicPacksWindow.h index 860bb267..73101f98 100644 --- a/src/gui/wxgui/DownloadGraphicPacksWindow.h +++ b/src/gui/wxgui/DownloadGraphicPacksWindow.h @@ -1,10 +1,12 @@ #pragma once #include <atomic> +#include <curl/system.h> #include <thread> #include <string> #include <memory> +#include <wx/dialog.h> #include <wx/timer.h> #include <wx/gauge.h> @@ -55,6 +57,6 @@ private: std::unique_ptr<curlDownloadFileState_t> m_downloadState; static size_t curlDownloadFile_writeData(void* ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState); - static int progress_callback(curlDownloadFileState_t* downloadState, double dltotal, double dlnow, double ultotal, double ulnow); + static int progress_callback(curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); static bool curlDownloadFile(const char* url, curlDownloadFileState_t* downloadState); }; diff --git a/src/gui/wxgui/GeneralSettings2.cpp b/src/gui/wxgui/GeneralSettings2.cpp index 4c83f698..12608e42 100644 --- a/src/gui/wxgui/GeneralSettings2.cpp +++ b/src/gui/wxgui/GeneralSettings2.cpp @@ -400,6 +400,55 @@ wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook) graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } + { + auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("Gamma settings")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + auto row = new wxFlexGridSizer(0, 2, 0, 0); + row->SetFlexibleDirection(wxBOTH); + row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + auto targetGammaLabel = new wxStaticText(box, wxID_ANY, _("Target Gamma")); + row->Add(targetGammaLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_overrideGammaValue = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f); + row->Add(m_overrideGammaValue, 0, wxALL, 5); + auto targetGammaTooltip = _("The display gamma to reproduce\nIf you are unsure, set this to 2.2"); + targetGammaLabel->SetToolTip(targetGammaTooltip); + m_overrideGammaValue->SetToolTip(targetGammaTooltip); + + + auto displayGammaLabel = new wxStaticText(box, wxID_ANY, _("Display Gamma")); + row->Add(displayGammaLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxBoxSizer* srgbCheckBoxSizer = new wxBoxSizer(wxHORIZONTAL); + row->Add(srgbCheckBoxSizer); + m_userDisplayGamma = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f); + + auto displayGammaTooltip = _("The gamma of your monitor\nIf you are unsure, set this to 2.2"); + m_userDisplayGamma->SetToolTip(displayGammaTooltip); + displayGammaLabel->SetToolTip(displayGammaTooltip); + + m_userDisplayisSRGB = new wxCheckBox(box, wxID_ANY, "sRGB", wxDefaultPosition, wxDefaultSize); + m_userDisplayisSRGB->SetToolTip(_("Select this if Cemu is being displayed using a piecewise sRGB gamma curve.\n" + "This is typically not the case so you can probably leave this unchecked.\n" + "Exceptions include HDR displays (with HDR enabled), calibrated SDR displays with Windows 11's Auto Color Management enabled, " + "or when using a display profile with a VCGT tag that targets piecewise sRGB.\n" + "When this box is selected Cemu will compensate for the piecewise curve to approximate the pure gamma curve of a TV.\n" + "Colors will be more accurate, especially in dark scenes, but this may result in banding or crushed shadows, " + "so it is best if you display Cemu with pure gamma and do not use this setting.")); + m_userDisplayisSRGB->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnUserDisplaySRGBSelected, this); + + srgbCheckBoxSizer->Add(m_userDisplayGamma, 0, wxALL, 5); + srgbCheckBoxSizer->Add(m_userDisplayisSRGB, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + row->Add(new wxStaticText(box, wxID_ANY, _("Override Gamma")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_overrideGamma = new wxCheckBox(box, wxID_ANY, "", wxDefaultPosition, {230, -1}); + m_overrideGamma->SetToolTip(_("Ignore title's gamma preference")); + row->Add(m_overrideGamma, 0, wxALL, 5); + + box_sizer->Add(row, 0, wxEXPAND, 5); + graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + { wxString choices[] = { _("Bilinear"), _("Bicubic"), _("Hermite"), _("Nearest Neighbor") }; m_upscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Upscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); @@ -1171,6 +1220,9 @@ void GeneralSettings2::StoreConfig() config.vsync = m_vsync->GetSelection(); + config.overrideAppGammaPreference = m_overrideGamma->IsChecked(); + config.overrideGammaValue = m_overrideGammaValue->GetValue(); + config.userDisplayGamma = m_userDisplayGamma->GetValue() * !m_userDisplayisSRGB->GetValue(); config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked(); config.force_mesh_shaders = m_force_mesh_shaders->IsChecked(); config.async_compile = m_async_compile->IsChecked(); @@ -1789,6 +1841,15 @@ void GeneralSettings2::ApplyConfig() // graphics m_graphic_api->SetSelection(config.graphic_api); m_vsync->SetSelection(config.vsync); + m_overrideGamma->SetValue(config.overrideAppGammaPreference); + m_overrideGammaValue->SetValue(config.overrideGammaValue); + m_userDisplayisSRGB->SetValue(config.userDisplayGamma == 0.0f); + m_userDisplayGamma->SetValue(config.userDisplayGamma); + if(m_userDisplayisSRGB->GetValue()) + { + m_userDisplayGamma->Disable(); + m_userDisplayGamma->SetValue(2.2f); + } m_async_compile->SetValue(config.async_compile); m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync); m_force_mesh_shaders->SetValue(config.force_mesh_shaders); @@ -2148,6 +2209,15 @@ void GeneralSettings2::OnGraphicAPISelected(wxCommandEvent& event) HandleGraphicsApiSelection(); } +void GeneralSettings2::OnUserDisplaySRGBSelected(wxCommandEvent& event) +{ + m_userDisplayGamma->SetValue(2.2f); + if(event.GetInt()) + m_userDisplayGamma->Disable(); + else + m_userDisplayGamma->Enable(); +} + void GeneralSettings2::OnAddPathClicked(wxCommandEvent& event) { wxDirDialog path_dialog(this, _("Select a directory containing games."), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); diff --git a/src/gui/wxgui/GeneralSettings2.h b/src/gui/wxgui/GeneralSettings2.h index dbd24433..b9f0be2a 100644 --- a/src/gui/wxgui/GeneralSettings2.h +++ b/src/gui/wxgui/GeneralSettings2.h @@ -56,6 +56,11 @@ private: // Graphics wxChoice* m_graphic_api, * m_graphic_device; wxChoice* m_vsync; + wxCheckBox* m_overrideGamma; + wxSpinCtrlDouble* m_overrideGammaValue; + wxSpinCtrlDouble* m_userDisplayGamma; + wxCheckBox* m_userDisplayisSRGB; + wxCheckBox *m_async_compile, *m_gx2drawdone_sync, *m_force_mesh_shaders; wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling; wxChoice* m_overlay_position, *m_notification_position, *m_overlay_scale, *m_notification_scale; @@ -97,6 +102,7 @@ private: void OnAudioDeviceSelected(wxCommandEvent& event); void OnAudioChannelsSelected(wxCommandEvent& event); void OnGraphicAPISelected(wxCommandEvent& event); + void OnUserDisplaySRGBSelected(wxCommandEvent& event); void OnAddPathClicked(wxCommandEvent& event); void OnRemovePathClicked(wxCommandEvent& event); void OnActiveAccountChanged(wxCommandEvent& event); diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index e0aa68ab..549f7c1a 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -86,6 +86,7 @@ enum MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, + MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, MAINFRAME_MENU_ID_FILE_EXIT, MAINFRAME_MENU_ID_FILE_END_EMULATION, MAINFRAME_MENU_ID_FILE_RECENT_0, @@ -184,6 +185,7 @@ EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder) +EVT_MENU(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, MainWindow::OnClearSpotPassCache) EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) @@ -703,8 +705,15 @@ void MainWindow::OnOpenFolder(wxCommandEvent& event) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath())); else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache"))); +} - +void MainWindow::OnClearSpotPassCache(wxCommandEvent& event) +{ + fs::path bossPath = ActiveSettings::GetMlcPath("usr/boss"); + fs::remove_all(bossPath); + // recreate usr/boss/ + fs::create_directory(bossPath); + wxMessageBox(_("SpotPass cache cleared")); } void MainWindow::OnInstallUpdate(wxCommandEvent& event) @@ -865,7 +874,7 @@ void MainWindow::OpenSettings() const bool mlc_modified = frame.MLCModified(); if (paths_modified) - m_game_list->ReloadGameEntries(false); + m_game_list->ReloadGameEntries(); else SaveSettings(); @@ -1010,7 +1019,7 @@ void MainWindow::OnConsoleLanguage(wxCommandEvent& event) if (m_game_list) { m_game_list->DeleteCachedStrings(); - m_game_list->ReloadGameEntries(false); + m_game_list->ReloadGameEntries(); } GetConfigHandle().Save(); } @@ -1456,7 +1465,7 @@ void MainWindow::OnAccountListRefresh(wxCommandEvent& event) void MainWindow::OnRequestGameListRefresh(wxCommandEvent& event) { - m_game_list->ReloadGameEntries(false); + m_game_list->ReloadGameEntries(); } void MainWindow::OnSetWindowTitle(wxCommandEvent& event) @@ -2128,7 +2137,8 @@ void MainWindow::RecreateMenu() m_loadMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_LOAD, _("&Load...")); m_installUpdateMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, _("&Install game title, update or DLC...")); - sint32 recentFileIndex = 0; + wxMenu* recentMenu = new wxMenu(); + sint32 recentFileIndex = 1; m_fileMenuSeparator0 = nullptr; m_fileMenuSeparator1 = nullptr; for (size_t i = 0; i < guiConfig.recent_launch_files.size(); i++) @@ -2136,15 +2146,21 @@ void MainWindow::RecreateMenu() const std::string& pathStr = guiConfig.recent_launch_files[i]; if (pathStr.empty()) continue; - if (recentFileIndex == 0) - m_fileMenuSeparator0 = m_fileMenu->AppendSeparator(); - m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, to_wxString(fmt::format("{}. {}", recentFileIndex, pathStr))); + recentMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, to_wxString(fmt::format("{}. {}", recentFileIndex, pathStr))); recentFileIndex++; - if (recentFileIndex >= 8) + if (recentFileIndex >= 10) break; } - m_fileMenuSeparator1 = m_fileMenu->AppendSeparator(); + if (recentFileIndex == 0) + { + wxMenuItem* placeholder = recentMenu->Append(wxID_NONE, _("(No recent files)")); + placeholder->Enable(false); + } + + m_fileMenu->AppendSeparator(); + m_fileMenu->AppendSubMenu(recentMenu, _("Recent files")); + m_fileMenu->AppendSeparator(); } else { @@ -2154,11 +2170,14 @@ void MainWindow::RecreateMenu() #endif } - m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder")); - m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder")); + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("Open Cemu folder")); + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("Open MLC folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder")); m_fileMenu->AppendSeparator(); - + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, _("Clear Spot&Pass cache"), wxEmptyString); + if (m_game_launched) + m_fileMenu->Enable(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, false); + m_fileMenu->AppendSeparator(); m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); m_menuBar->Append(m_fileMenu, _("&File")); // options->account submenu diff --git a/src/gui/wxgui/MainWindow.h b/src/gui/wxgui/MainWindow.h index 07464188..0e80b16d 100644 --- a/src/gui/wxgui/MainWindow.h +++ b/src/gui/wxgui/MainWindow.h @@ -94,6 +94,7 @@ public: void OnClose(wxCloseEvent& event); void OnFileMenu(wxCommandEvent& event); void OnOpenFolder(wxCommandEvent& event); + void OnClearSpotPassCache(wxCommandEvent& event); void OnLaunchFromFile(wxLaunchGameEvent& event); void OnInstallUpdate(wxCommandEvent& event); void OnFileExit(wxCommandEvent& event); diff --git a/src/gui/wxgui/MemorySearcherTool.cpp b/src/gui/wxgui/MemorySearcherTool.cpp index cc99fb8b..75f756c7 100644 --- a/src/gui/wxgui/MemorySearcherTool.cpp +++ b/src/gui/wxgui/MemorySearcherTool.cpp @@ -382,7 +382,7 @@ void MemorySearcherTool::OnEntryListRightClick(wxDataViewEvent& event) //mnu.SetClientData(data); mnu.Append(LIST_ENTRY_ADD, _("&Add new entry"))->Enable(false); mnu.Append(LIST_ENTRY_REMOVE, _("&Remove entry")); - mnu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemorySearcherTool::OnPopupClick), nullptr, this); + mnu.Bind(wxEVT_COMMAND_MENU_SELECTED, &MemorySearcherTool::OnPopupClick, this); PopupMenu(&mnu); } diff --git a/src/gui/wxgui/MemorySearcherTool.h b/src/gui/wxgui/MemorySearcherTool.h index 77820524..3d6c0d87 100644 --- a/src/gui/wxgui/MemorySearcherTool.h +++ b/src/gui/wxgui/MemorySearcherTool.h @@ -3,6 +3,7 @@ #include <mutex> #include <thread> #include <atomic> +#include <future> #include <wx/listctrl.h> diff --git a/src/gui/wxgui/components/wxGameList.cpp b/src/gui/wxgui/components/wxGameList.cpp index 89063d9b..79a16843 100644 --- a/src/gui/wxgui/components/wxGameList.cpp +++ b/src/gui/wxgui/components/wxGameList.cpp @@ -66,9 +66,9 @@ void _stripPathFilename(fs::path& path) path = path.parent_path(); } -std::list<fs::path> _getCachesPaths(const TitleId& titleId) +std::vector<fs::path> _getCachesPaths(const TitleId& titleId) { - std::list<fs::path> cachePaths{ + std::vector<fs::path> cachePaths{ ActiveSettings::GetCachePath(L"shaderCache/driver/vk/{:016x}.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/precompiled/{:016x}_spirv.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/precompiled/{:016x}_gl.bin", titleId), @@ -78,12 +78,12 @@ std::list<fs::path> _getCachesPaths(const TitleId& titleId) ActiveSettings::GetCachePath(L"shaderCache/transferable/{:016x}_vkpipeline.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/transferable/{:016x}_mtlpipeline.bin", titleId)}; - cachePaths.remove_if( - [](const fs::path& cachePath) - { - std::error_code ec; - return !fs::exists(cachePath, ec); - }); + cachePaths.erase(std::remove_if(cachePaths.begin(), cachePaths.end(), + [](const fs::path& cachePath) { + std::error_code ec; + return !fs::exists(cachePath, ec); + }), + cachePaths.end()); return cachePaths; } @@ -144,17 +144,15 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id) const auto& config = GetWxGUIConfig(); char transparent_bitmap[kIconWidth * kIconWidth * 4] = {}; - memset((void*)transparent_bitmap, wxSystemSettings::GetAppearance().IsDark() ? 0xFF : 0x00, sizeof(transparent_bitmap)); + memset(transparent_bitmap, wxSystemSettings::GetAppearance().IsDark() ? 0xFF : 0x00, sizeof(transparent_bitmap)); wxBitmap blank(transparent_bitmap, kIconWidth, kIconWidth); - m_image_list_data = {}; - m_image_list_data.emplace_back(wxBitmapBundle::FromBitmap(blank)); - wxListCtrl::SetNormalImages(m_image_list_data); + m_image_list_data.Add(blank); + wxListCtrl::SetImageList(&m_image_list_data, wxIMAGE_LIST_NORMAL); - m_image_list_small_data = {}; wxBitmap::Rescale(blank, {kListIconWidth, kListIconWidth}); - m_image_list_small_data.emplace_back(wxBitmapBundle::FromBitmap(blank)); - wxListCtrl::SetSmallImages(m_image_list_small_data); + m_image_list_small_data.Add(blank); + wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_SMALL); InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0); if(config.show_icon_column) @@ -305,12 +303,6 @@ int wxGameList::GetColumnDefaultWidth(int column) switch (column) { case ColumnIcon: -#if __WXMSW__ - // note: this is another workaround that could be used to fix the icon offset, but instead of this there's a vcpkg patch for wxWidgets that fixes the icon offset - // wxWidgets offsets the icon in the REPORT view so it's cut off if the column width is set to 64px, see https://github.com/wxWidgets/wxWidgets/blob/09f433faf39aab3f25b3c564b82448bb845fae56/src/msw/listctrl.cpp#L3091 - // so add 6px to the column width to compensate, and add another 6px to the right side so that it has equal whitespace on both sides - // return kListIconWidth + 6 + 6; -#endif return kListIconWidth; case ColumnName: return DefaultColumnSize::name; @@ -353,7 +345,7 @@ bool wxGameList::IsVisible(long item) const return visible; } -void wxGameList::ReloadGameEntries(bool cached) +void wxGameList::ReloadGameEntries() { wxWindowUpdateLocker windowlock(this); DeleteAllItems(); @@ -413,11 +405,13 @@ void wxGameList::SetStyle(Style style, bool save) switch(style) { case Style::kIcons: - wxListCtrl::SetNormalImages(m_image_list_data); + wxListCtrl::SetImageList(&m_image_list_data, wxIMAGE_LIST_NORMAL); break; case Style::kSmallIcons: + wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_NORMAL); + break; case Style::kList: - wxListCtrl::SetSmallImages(m_image_list_small_data); + wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_SMALL); break; } @@ -446,7 +440,7 @@ long wxGameList::GetStyleFlags(Style style) const switch (style) { case Style::kList: - return (wxLC_SINGLE_SEL | wxLC_REPORT); + return (wxLC_SINGLE_SEL | wxLC_VRULES | wxLC_REPORT); case Style::kIcons: return (wxLC_SINGLE_SEL | wxLC_ICON); case Style::kSmallIcons: @@ -461,25 +455,22 @@ void wxGameList::UpdateItemColors(sint32 startIndex) { wxWindowUpdateLocker lock(this); - wxColour bgColourPrimary = GetBackgroundColour(); - wxColour bgColourSecondary = wxHelper::CalculateAccentColour(bgColourPrimary); - for (int i = startIndex; i < GetItemCount(); ++i) { - const auto titleId = (uint64)GetItemData(i); + const uint64 titleId = GetItemData(i); if (GetConfig().IsGameListFavorite(titleId)) { SetItemBackgroundColour(i, kFavoriteColor); SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } - else if ((i&1) != 0) + else if ((i % 2) != 0) { - SetItemBackgroundColour(i, bgColourPrimary); + SetItemBackgroundColour(i, kPrimaryColor); SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } else { - SetItemBackgroundColour(i, bgColourSecondary); + SetItemBackgroundColour(i, kAlternateColor); SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } } @@ -487,7 +478,7 @@ void wxGameList::UpdateItemColors(sint32 startIndex) static inline int order_to_int(const std::weak_ordering &wo) { - // no easy conversion seems to exists in C++20 + // no easy conversion seems to exist in C++20 if (wo == std::weak_ordering::less) return -1; else if (wo == std::weak_ordering::greater) @@ -499,27 +490,24 @@ std::weak_ordering wxGameList::SortComparator(uint64 titleId1, uint64 titleId2, { auto titleLastPlayed = [](uint64_t id) { - iosu::pdm::GameListStat playTimeStat{}; - iosu::pdm::GetStatForGamelist(id, playTimeStat); - return playTimeStat; + iosu::pdm::GameListStat playTimeStat{}; + iosu::pdm::GetStatForGamelist(id, playTimeStat); + return playTimeStat; }; auto titlePlayMinutes = [](uint64_t id) { - iosu::pdm::GameListStat playTimeStat; - if (!iosu::pdm::GetStatForGamelist(id, playTimeStat)) - return 0u; - return playTimeStat.numMinutesPlayed; + iosu::pdm::GameListStat playTimeStat; + if (!iosu::pdm::GetStatForGamelist(id, playTimeStat)) + return 0u; + return playTimeStat.numMinutesPlayed; }; auto titleRegion = [](uint64_t id) { - return CafeTitleList::GetGameInfo(id).GetRegion(); + return CafeTitleList::GetGameInfo(id).GetRegion(); }; - if (!sortData->asc) - std::swap(titleId1, titleId2); - switch(sortData->column) { default: @@ -548,7 +536,7 @@ std::weak_ordering wxGameList::SortComparator(uint64 titleId1, uint64 titleId2, int wxGameList::SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) { const auto sort_data = (SortData*)sortData; - return order_to_int(sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data)); + return sort_data->dir * order_to_int(sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data)); } void wxGameList::SortEntries(int column) @@ -572,7 +560,7 @@ void wxGameList::SortEntries(int column) case ColumnRegion: case ColumnTitleID: { - SortData data{this, ItemColumns{column}, ascending}; + SortData data{this, ItemColumns{column}, ascending ? 1 : -1}; SortItems(SortFunction, (wxIntPtr)&data); ShowSortIndicator(column, ascending); break; @@ -843,8 +831,8 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) int icon_small; if (!QueryIconForTitle(title_id, icon_large, icon_small)) break; - auto icon = m_image_list_data[icon_large]; - auto newClipboardData = wxBitmapDataObject(icon.GetBitmap(icon.GetDefaultSize())); + auto icon = m_image_list_data.GetIcon(icon_large); + auto newClipboardData = wxBitmapDataObject(icon); wxClipboard::Get()->SetData(&newClipboardData); wxClipboard::Get()->Close(); } @@ -857,7 +845,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) switch (event.GetId()) { case kContextMenuRefreshGames: - ReloadGameEntries(false); + ReloadGameEntries(); break; case kContextMenuStyleList: SetStyle(Style::kList); @@ -1005,7 +993,7 @@ void wxGameList::ApplyGameListColumnWidths() const auto& config = GetWxGUIConfig(); wxWindowUpdateLocker lock(this); if(config.show_icon_column) - SetColumnWidth(ColumnIcon, GetColumnDefaultWidth(ColumnIcon)); + SetColumnWidth(ColumnIcon, kListIconWidth); else SetColumnWidth(ColumnIcon, 0); SetColumnWidth(ColumnName, config.column_width.name); @@ -1120,19 +1108,6 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) TitleId baseTitleId = gameInfo.GetBaseTitleId(); bool isNewEntry = false; - if (m_style == Style::kIcons) - { - wxListCtrl::SetNormalImages(m_image_list_data); - } - else if (m_style == Style::kSmallIcons) - { - wxListCtrl::SetSmallImages(m_image_list_small_data); - } - else if (m_style == Style::kList) - { - wxListCtrl::SetSmallImages(m_image_list_small_data); - } - int icon = -1; /* 0 is the default empty icon */ int icon_small = -1; /* 0 is the default empty icon */ QueryIconForTitle(baseTitleId, icon, icon_small); @@ -1283,7 +1258,7 @@ void wxGameList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) } } -void wxGameList::RemoveCache(const std::list<fs::path>& cachePaths, const std::string& titleName) +void wxGameList::RemoveCache(const std::vector<fs::path>& cachePaths, const std::string& titleName) { wxMessageDialog dialog(this, formatWxString(_("Remove the shader caches for {}?"), titleName), _("Remove shader caches"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); dialog.SetYesNoLabels(_("Yes"), _("No")); @@ -1291,7 +1266,7 @@ void wxGameList::RemoveCache(const std::list<fs::path>& cachePaths, const std::s const auto dialogResult = dialog.ShowModal(); if (dialogResult != wxID_YES) return; - std::list<std::string> errs; + std::vector<std::string> errs; for (const fs::path& cachePath : cachePaths) { if (std::error_code ec; !fs::remove(cachePath, ec)) @@ -1348,18 +1323,17 @@ void wxGameList::AsyncWorkerThread() wxMemoryInputStream tmp_stream(tgaData->data(), tgaData->size()); const wxImage image(tmp_stream); // todo - is wxImageList thread safe? - m_image_list_data.emplace_back(image.Scale(kIconWidth, kIconWidth, wxIMAGE_QUALITY_BICUBIC)); - m_image_list_small_data.emplace_back(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); + int icon = m_image_list_data.Add(image.Scale(kIconWidth, kIconWidth, wxIMAGE_QUALITY_BICUBIC)); + int icon_small = m_image_list_small_data.Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); // store in cache m_icon_cache_mtx.lock(); - m_icon_cache.try_emplace(titleId, m_image_list_data.size() - 1, m_image_list_small_data.size() - 1); + m_icon_cache.try_emplace(titleId, icon, icon_small); m_icon_cache_mtx.unlock(); iconSuccessfullyLoaded = true; } else { cemuLog_log(LogType::Force, "Failed to load icon for title {:016x}", titleId); - cemu_assert_debug(false); } titleInfo.Unmount(tempMountPath); // notify UI about loaded icon @@ -1418,9 +1392,9 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) // Obtain and convert icon [&]() { - int iconIndex, smallIconIndex; + int iconIdx, smallIconIdx; - if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex)) + if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) { cemuLog_log(LogType::Force, "Icon hasn't loaded"); return; @@ -1436,8 +1410,8 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); - const auto icon = m_image_list_data[iconIndex]; - wxBitmap bitmap{icon.GetBitmap(wxDefaultSize)}; + const auto icon = m_image_list_data.GetIcon(iconIdx); + wxBitmap bitmap{icon}; wxImage image = bitmap.ConvertToImage(); wxPNGHandler pngHandler; if (!pngHandler.SaveFile(&image, pngFileStream, false)) @@ -1511,9 +1485,9 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) // Obtain and convert icon [&]() { - int iconIndex, smallIconIndex; + int iconIdx, smallIconIdx; - if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex)) + if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) { cemuLog_log(LogType::Force, "Icon hasn't loaded"); return; @@ -1529,8 +1503,8 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); - const auto icon = m_image_list_data[iconIndex]; - wxBitmap bitmap{icon.GetBitmap(wxDefaultSize)}; + const auto icon = m_image_list_data.GetIcon(iconIdx); + wxBitmap bitmap{icon}; wxImage image = bitmap.ConvertToImage(); wxPNGHandler pngHandler; if (!pngHandler.SaveFile(&image, pngFileStream, false)) @@ -1638,14 +1612,14 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) cemuLog_log(LogType::Force, "Icon hasn't loaded"); return; } - const auto icon = m_image_list_data[iconIdx]; + const auto icon = m_image_list_data.GetIcon(iconIdx); const auto folder = ActiveSettings::GetUserDataPath("icons"); if (!fs::exists(folder) && !fs::create_directories(folder)) { cemuLog_log(LogType::Force, "Failed to create icon directory"); return; } - wxBitmap bitmap{icon.GetBitmap(wxDefaultSize)}; + wxBitmap bitmap{icon}; icon_path = folder / fmt::format("{:016x}.ico", titleId); auto stream = wxFileOutputStream(icon_path->wstring()); diff --git a/src/gui/wxgui/components/wxGameList.h b/src/gui/wxgui/components/wxGameList.h index e4f90ca3..3c5fdedd 100644 --- a/src/gui/wxgui/components/wxGameList.h +++ b/src/gui/wxgui/components/wxGameList.h @@ -3,15 +3,15 @@ #include "config/CemuConfig.h" #include "Cafe/TitleList/TitleId.h" -#include <future> #include <mutex> -#include <optional> #include <wx/listctrl.h> #include <wx/timer.h> #include <wx/panel.h> #include <wx/settings.h> #include <Cafe/TitleList/GameInfo.h> + +#include "wxHelper.h" #include "util/helpers/Semaphore.h" class wxTitleIdEvent : public wxCommandEvent @@ -51,7 +51,7 @@ public: void SaveConfig(bool flush = false); bool IsVisible(long item) const; // only available in wxwidgets 3.1.3 - void ReloadGameEntries(bool cached = false); + void ReloadGameEntries(); void DeleteCachedStrings(); void CreateShortcut(GameInfo2& gameInfo); @@ -66,7 +66,8 @@ private: const wxColour kUpdateColor{ wxSystemSettings::SelectLightDark(wxColour(195, 57, 57), wxColour(84, 29, 29)) }; const wxColour kFavoriteColor{ wxSystemSettings::SelectLightDark(wxColour(253, 246, 211), wxColour(82, 84, 48)) }; - const wxColour kSecondColor{ wxSystemSettings::SelectLightDark(wxColour(242, 249, 253), wxColour(34, 34, 34)) }; + const wxColour kPrimaryColor = GetBackgroundColour(); + const wxColour kAlternateColor = wxHelper::CalculateAccentColour(kPrimaryColor); void UpdateItemColors(sint32 startIndex = 0); enum ItemColumns : int @@ -89,7 +90,7 @@ private: { wxGameList* thisptr; ItemColumns column; - bool asc; + int dir; }; int FindInsertPosition(TitleId titleId); @@ -109,12 +110,12 @@ private: std::vector<TitleId> m_icon_load_queue; uint64 m_callbackIdTitleList; - + std::string GetNameByTitleId(uint64 titleId); void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); - void RemoveCache(const std::list<fs::path>& cachePath, const std::string& titleName); + void RemoveCache(const std::vector<fs::path>& cachePath, const std::string& titleName); void AsyncWorkerThread(); void RequestLoadIconAsync(TitleId titleId); @@ -122,7 +123,8 @@ private: inline static constexpr int kListIconWidth = 64; inline static constexpr int kIconWidth = 128; - wxWithImages::Images m_image_list_data, m_image_list_small_data; + wxImageList m_image_list_data = wxImageList(kIconWidth, kIconWidth, false, 1); + wxImageList m_image_list_small_data = wxImageList(kListIconWidth, kListIconWidth, false, 1); std::mutex m_icon_cache_mtx; std::set<TitleId> m_icon_loaded; diff --git a/src/gui/wxgui/debugger/BreakpointWindow.cpp b/src/gui/wxgui/debugger/BreakpointWindow.cpp index 7c4b1556..63d5a733 100644 --- a/src/gui/wxgui/debugger/BreakpointWindow.cpp +++ b/src/gui/wxgui/debugger/BreakpointWindow.cpp @@ -223,7 +223,7 @@ void BreakpointWindow::OnRightDown(wxMouseEvent& event) menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); - menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this); + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &BreakpointWindow::OnContextMenuClick, this); PopupMenu(&menu); } else @@ -234,7 +234,7 @@ void BreakpointWindow::OnRightDown(wxMouseEvent& event) wxMenu menu; menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint")); - menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClickSelected), nullptr, this); + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &BreakpointWindow::OnContextMenuClickSelected, this); PopupMenu(&menu); } } diff --git a/src/gui/wxgui/input/HotkeySettings.cpp b/src/gui/wxgui/input/HotkeySettings.cpp index 195061d7..0bf59025 100644 --- a/src/gui/wxgui/input/HotkeySettings.cpp +++ b/src/gui/wxgui/input/HotkeySettings.cpp @@ -239,8 +239,8 @@ void HotkeySettings::CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotke controllerInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnControllerHotkeyInputLeftClick, this); /* for cancelling and clearing input */ - keyInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnKeyboardHotkeyInputRightClick), NULL, this); - controllerInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnControllerHotkeyInputRightClick), NULL, this); + keyInput->Bind(wxEVT_RIGHT_UP, &HotkeySettings::OnKeyboardHotkeyInputRightClick, this); + controllerInput->Bind(wxEVT_RIGHT_UP, &HotkeySettings::OnControllerHotkeyInputRightClick, this); keyInput->SetMinSize(m_minButtonSize); controllerInput->SetMinSize(m_minButtonSize); diff --git a/src/gui/wxgui/input/InputSettings2.cpp b/src/gui/wxgui/input/InputSettings2.cpp index 8fbc5027..16a7381d 100644 --- a/src/gui/wxgui/input/InputSettings2.cpp +++ b/src/gui/wxgui/input/InputSettings2.cpp @@ -21,7 +21,9 @@ #include "wxgui/input/InputAPIAddWindow.h" #include "input/ControllerFactory.h" +#ifdef HAS_BLUEZ #include "wxgui/input/PairingDialog.h" +#endif #include "wxgui/input/panels/VPADInputPanel.h" #include "wxgui/input/panels/ProControllerInputPanel.h" @@ -255,12 +257,14 @@ wxWindow* InputSettings2::initialize_page(size_t index) page_data.m_controller_api_remove = remove_api; } +#ifdef HAS_BLUEZ auto* pairingDialog = new wxButton(page, wxID_ANY, _("Pair Wii/Wii U Controller")); pairingDialog->Bind(wxEVT_BUTTON, [this](wxEvent&) { PairingDialog pairing_dialog(this); pairing_dialog.ShowModal(); }); sizer->Add(pairingDialog, wxGBPosition(5, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); +#endif // controller auto* controller_bttns = new wxBoxSizer(wxHORIZONTAL); diff --git a/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index 323eda39..ae1ea971 100644 --- a/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -124,7 +124,7 @@ DebugPPCThreadsWindow::DebugPPCThreadsWindow(wxFrame& parent) sizer->Add(row, 0, wxEXPAND | wxALL, 5); - m_thread_list->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(DebugPPCThreadsWindow::OnThreadListRightClick), nullptr, this); + m_thread_list->Bind(wxEVT_RIGHT_DOWN, &DebugPPCThreadsWindow::OnThreadListRightClick, this); SetSizer(sizer); @@ -466,7 +466,7 @@ void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) menu.AppendSeparator(); menu.Append(THREADLIST_MENU_DUMP_STACK_TRACE, _("Write stack trace to log")); menu.Append(THREADLIST_MENU_PROFILE_THREAD, _("Profile thread")); - menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(DebugPPCThreadsWindow::OnThreadListPopupClick), nullptr, this); + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &DebugPPCThreadsWindow::OnThreadListPopupClick, this); PopupMenu(&menu); } diff --git a/src/gui/wxgui/windows/TextureRelationViewer/TextureRelationWindow.cpp b/src/gui/wxgui/windows/TextureRelationViewer/TextureRelationWindow.cpp index d0169ab0..cc1e3321 100644 --- a/src/gui/wxgui/windows/TextureRelationViewer/TextureRelationWindow.cpp +++ b/src/gui/wxgui/windows/TextureRelationViewer/TextureRelationWindow.cpp @@ -124,7 +124,7 @@ TextureRelationViewerWindow::TextureRelationViewerWindow(wxFrame& parent) sizerBottom->Add(checkboxShowViews, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM | wxTOP | wxLEFT, 10); checkboxShowViews->SetValue(true); - textureRelationListA->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(TextureRelationViewerWindow::OnTextureListRightClick), NULL, this); + textureRelationListA->Bind(wxEVT_RIGHT_DOWN, &TextureRelationViewerWindow::OnTextureListRightClick, this); sizer->Add( sizerBottom, diff --git a/src/gui/wxgui/wxHelper.h b/src/gui/wxgui/wxHelper.h index 06315756..0a14c9ba 100644 --- a/src/gui/wxgui/wxHelper.h +++ b/src/gui/wxgui/wxHelper.h @@ -25,10 +25,9 @@ namespace wxHelper inline wxColour CalculateAccentColour(const wxColour& bgColour) { - wxColour bgColourSecondary; const uint32 bgLightness = (bgColour.GetRed() + bgColour.GetGreen() + bgColour.GetBlue()) / 3; const bool isDarkTheme = bgLightness < 128; - bgColourSecondary = bgColour.ChangeLightness(isDarkTheme ? 110 : 90); // color for even rows + wxColour bgColourSecondary = bgColour.ChangeLightness(isDarkTheme ? 110 : 90); // color for even rows // for very light themes we'll use a blue tint to match the older Windows Cemu look if (bgLightness > 250) bgColourSecondary = wxColour(bgColour.Red() - 13, bgColour.Green() - 6, bgColour.Blue() - 2); diff --git a/src/gui/wxgui/wxcomponents/checktree.cpp b/src/gui/wxgui/wxcomponents/checktree.cpp index e368fa6a..d7d56b69 100644 --- a/src/gui/wxgui/wxcomponents/checktree.cpp +++ b/src/gui/wxgui/wxcomponents/checktree.cpp @@ -121,49 +121,49 @@ void wxCheckTree::Init() // Unchecked renderer_dc.SelectObject(unchecked_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_NONE); // Unchecked Mouse Over renderer_dc.SelectObject(unchecked_mouse_over_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CURRENT); // Unchecked and Disabled renderer_dc.SelectObject(unchecked_disabled_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_DISABLED); // Unchecked Left Down renderer_dc.SelectObject(unchecked_left_down_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CURRENT | wxCONTROL_PRESSED); // Checked renderer_dc.SelectObject(checked_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED); // Checked Mouse Over renderer_dc.SelectObject(checked_mouse_over_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_CURRENT); // Checked Left Down renderer_dc.SelectObject(checked_left_down_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_CURRENT | wxCONTROL_PRESSED); // Checked and Disabled renderer_dc.SelectObject(checked_disabled_bmp); - renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID)); + renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_DISABLED); diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt index 86aeb130..57526c18 100644 --- a/src/imgui/CMakeLists.txt +++ b/src/imgui/CMakeLists.txt @@ -31,10 +31,6 @@ target_sources(imguiImpl PRIVATE target_include_directories(imguiImpl PUBLIC "../../dependencies/imgui/") target_link_libraries(imguiImpl PRIVATE - CemuCafe CemuCommon CemuGui - CemuInput - CemuResource - CemuUtil ) diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 3158ce99..cdf9cbba 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -88,16 +88,8 @@ endif () target_include_directories(CemuInput PUBLIC "../") target_link_libraries(CemuInput PRIVATE - CemuCafe CemuCommon - CemuConfig CemuGui - CemuUtil - Boost::headers - Boost::program_options - glm::glm - pugixml::pugixml - SDL2::SDL2 ) if (ENABLE_HIDAPI) @@ -106,4 +98,4 @@ endif() if (ENABLE_BLUEZ) target_link_libraries(CemuInput PRIVATE bluez::bluez) -endif () \ No newline at end of file +endif () diff --git a/src/input/api/DirectInput/DirectInputController.cpp b/src/input/api/DirectInput/DirectInputController.cpp index 0792aac7..f0f33701 100644 --- a/src/input/api/DirectInput/DirectInputController.cpp +++ b/src/input/api/DirectInput/DirectInputController.cpp @@ -22,9 +22,10 @@ DirectInputController::~DirectInputController() // TODO: test if really needed // workaround for gamecube controllers crash on release? bool should_release_device = true; - if (m_product_guid == GUID{}) { - DIDEVICEINSTANCE info{}; - info.dwSize = sizeof(DIDEVICEINSTANCE); + if (m_product_guid == GUID{}) + { + DIDEVICEINSTANCEW info{}; + info.dwSize = sizeof(DIDEVICEINSTANCEW); if (SUCCEEDED(m_device->GetDeviceInfo(&info))) { m_product_guid = info.guidProduct; @@ -90,8 +91,8 @@ bool DirectInputController::connect() if (FAILED(hr) || m_device == nullptr) return false; - DIDEVICEINSTANCE idi{}; - idi.dwSize = sizeof(DIDEVICEINSTANCE); + DIDEVICEINSTANCEW idi{}; + idi.dwSize = sizeof(DIDEVICEINSTANCEW); if (SUCCEEDED(m_device->GetDeviceInfo(&idi))) { // overwrite guid name with "real" display name @@ -147,8 +148,8 @@ bool DirectInputController::connect() } } - DIDEVICEINSTANCE info{}; - info.dwSize = sizeof(DIDEVICEINSTANCE); + DIDEVICEINSTANCEW info{}; + info.dwSize = sizeof(DIDEVICEINSTANCEW); if (SUCCEEDED(m_device->GetDeviceInfo(&info))) { m_product_guid = info.guidProduct; @@ -157,7 +158,7 @@ bool DirectInputController::connect() std::fill(m_min_axis.begin(), m_min_axis.end(), 0); std::fill(m_max_axis.begin(), m_max_axis.end(), std::numeric_limits<uint16>::max()); m_device->EnumObjects( - [](LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) -> BOOL + [](LPCDIDEVICEOBJECTINSTANCEW lpddoi, LPVOID pvRef) -> BOOL { auto* thisptr = (DirectInputController*)pvRef; diff --git a/src/input/api/DirectInput/DirectInputController.h b/src/input/api/DirectInput/DirectInputController.h index d2c3dba2..ac617356 100644 --- a/src/input/api/DirectInput/DirectInputController.h +++ b/src/input/api/DirectInput/DirectInputController.h @@ -42,7 +42,7 @@ private: GUID m_product_guid{}; std::shared_mutex m_mutex; - Microsoft::WRL::ComPtr<IDirectInputDevice8> m_device; + Microsoft::WRL::ComPtr<IDirectInputDevice8W> m_device; Microsoft::WRL::ComPtr<IDirectInputEffect> m_effect; std::array<LONG, 6> m_min_axis{}; diff --git a/src/input/api/DirectInput/DirectInputControllerProvider.cpp b/src/input/api/DirectInput/DirectInputControllerProvider.cpp index 79f31354..4c7192f6 100644 --- a/src/input/api/DirectInput/DirectInputControllerProvider.cpp +++ b/src/input/api/DirectInput/DirectInputControllerProvider.cpp @@ -4,34 +4,16 @@ DirectInputControllerProvider::DirectInputControllerProvider() { - /*m_module = LoadLibraryA("dinput8.dll"); - if (!m_module) - throw std::runtime_error("can't load any xinput dll"); - - m_DirectInput8Create = (decltype(&DirectInput8Create))GetProcAddress(m_module, "DirectInput8Create"); - m_GetdfDIJoystick = (decltype(&GetdfDIJoystick))GetProcAddress(m_module, "GetdfDIJoystick"); - - if (!m_DirectInput8Create) - { - FreeLibrary(m_module); - throw std::runtime_error("can't find the DirectInput8Create export"); - }*/ - - - const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_dinput8, nullptr); + const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W, (void**)&m_dinput8, nullptr); if (FAILED(r)) { const auto error = GetLastError(); - //FreeLibrary(m_module); throw std::runtime_error(fmt::format("can't create direct input object (error: {:#x})", error)); } } DirectInputControllerProvider::~DirectInputControllerProvider() { - /*if (m_module) - FreeLibrary(m_module); - */ } std::vector<std::shared_ptr<ControllerBase>> DirectInputControllerProvider::get_controllers() @@ -39,7 +21,7 @@ std::vector<std::shared_ptr<ControllerBase>> DirectInputControllerProvider::get_ std::vector<std::shared_ptr<ControllerBase>> result; m_dinput8->EnumDevices(DI8DEVCLASS_GAMECTRL, - [](LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) -> BOOL + [](LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef) -> BOOL { auto* controllers = (decltype(&result))pvRef; @@ -55,8 +37,5 @@ std::vector<std::shared_ptr<ControllerBase>> DirectInputControllerProvider::get_ LPCDIDATAFORMAT DirectInputControllerProvider::get_data_format() const { - /*if (m_GetdfDIJoystick) - return m_GetdfDIJoystick();*/ - return GetdfDIJoystick(); } diff --git a/src/input/api/DirectInput/DirectInputControllerProvider.h b/src/input/api/DirectInput/DirectInputControllerProvider.h index de8c3216..aa601e84 100644 --- a/src/input/api/DirectInput/DirectInputControllerProvider.h +++ b/src/input/api/DirectInput/DirectInputControllerProvider.h @@ -23,7 +23,7 @@ public: std::vector<std::shared_ptr<ControllerBase>> get_controllers() override; - IDirectInput8* get_dinput() const { return m_dinput8.Get(); } + IDirectInput8W* get_dinput() const { return m_dinput8.Get(); } LPCDIDATAFORMAT get_data_format() const; private: @@ -32,7 +32,7 @@ private: decltype(&DirectInput8Create) m_DirectInput8Create; decltype(&GetdfDIJoystick) m_GetdfDIJoystick = nullptr; - Microsoft::WRL::ComPtr<IDirectInput8> m_dinput8; + Microsoft::WRL::ComPtr<IDirectInput8W> m_dinput8; }; #endif diff --git a/src/main.cpp b/src/main.cpp index ed48252c..9355465e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,12 +96,12 @@ void WindowsInitCwd() { #if BOOST_OS_WINDOWS executablePath.resize(4096); - int i = GetModuleFileName(NULL, executablePath.data(), executablePath.size()); + int i = GetModuleFileNameW(NULL, executablePath.data(), executablePath.size()); if(i >= 0) executablePath.resize(i); else executablePath.clear(); - SetCurrentDirectory(executablePath.c_str()); + SetCurrentDirectoryW(executablePath.c_str()); // set high priority SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); #endif @@ -192,7 +192,7 @@ void HandlePostUpdate() HANDLE lock; do { - lock = CreateMutex(nullptr, TRUE, L"Global\\cemu_update_lock"); + lock = CreateMutexW(nullptr, TRUE, L"Global\\cemu_update_lock"); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } while (lock == nullptr); const DWORD wait_result = WaitForSingleObject(lock, 2000); @@ -220,7 +220,7 @@ void ToolShaderCacheMerger(); #if BOOST_OS_WINDOWS // entrypoint for release builds -int wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nShowCmd) +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) { if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE))) cemuLog_log(LogType::Force, "CoInitializeEx() failed"); diff --git a/src/resource/CMakeLists.txt b/src/resource/CMakeLists.txt index 78d7c6db..c8833d01 100644 --- a/src/resource/CMakeLists.txt +++ b/src/resource/CMakeLists.txt @@ -22,4 +22,4 @@ target_sources(CemuResource PRIVATE CafeDefaultFont.cpp) target_include_directories(CemuResource PUBLIC "../") -target_link_libraries(CemuResource PRIVATE CemuCommon CemuComponents) +target_link_libraries(CemuResource PRIVATE CemuCommon) diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 7a39fe72..c0c45eba 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -92,9 +92,5 @@ set_property(TARGET CemuUtil PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CON target_include_directories(CemuUtil PUBLIC "../") target_link_libraries(CemuUtil PRIVATE - CemuCommon - CemuConfig - Boost::headers - Boost::nowide - OpenSSL::Crypto + CemuCommon ) diff --git a/src/util/helpers/helpers.h b/src/util/helpers/helpers.h index f4728f28..359602b4 100644 --- a/src/util/helpers/helpers.h +++ b/src/util/helpers/helpers.h @@ -3,6 +3,7 @@ #include <charconv> #include <filesystem> #include <string_view> +#include <set> #include "util/math/vector2.h" #include "util/math/vector3.h" diff --git a/vcpkg.json b/vcpkg.json index 8ee42a4f..4d34981d 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -34,13 +34,7 @@ "default-features": false }, "zstd", - { - "name": "wxwidgets", - "features": [ - "debug-support" - ], - "default-features": false - }, + "wxwidgets", "openssl", { "name": "curl",