devoptab: add SFTP. fs: disable stdio buffering. cmake: add options to disable components of sphaira, add new "lite" build with minimal features.

This commit is contained in:
ITotalJustice
2025-09-09 18:39:03 +01:00
parent 1695d69aa3
commit 931531e799
23 changed files with 11995 additions and 391 deletions

View File

@@ -21,6 +21,35 @@
"LTO": true
}
},
{
"name": "Lite",
"displayName": "Lite",
"inherits":["core"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "MinSizeRel",
"LTO": true,
"ENABLE_NVJPG": false,
"ENABLE_NSZ": false,
"ENABLE_LIBUSBHSFS": false,
"ENABLE_LIBUSBDVD": false,
"ENABLE_FTPSRV": false,
"ENABLE_LIBHAZE": false,
"ENABLE_AUDIO_MP3": false,
"ENABLE_AUDIO_OGG": false,
"ENABLE_AUDIO_WAV": false,
"ENABLE_AUDIO_FLAC": false,
"ENABLE_DEVOPTAB_HTTP": false,
"ENABLE_DEVOPTAB_NFS": false,
"ENABLE_DEVOPTAB_SMB2": false,
"ENABLE_DEVOPTAB_FTP": false,
"ENABLE_DEVOPTAB_SFTP": false,
"ENABLE_DEVOPTAB_WEBDAV": false
}
},
{
"name": "Dev",
"displayName": "Dev",
@@ -38,6 +67,11 @@
"configurePreset": "Release",
"jobs": 16
},
{
"name": "Lite",
"configurePreset": "Lite",
"jobs": 16
},
{
"name": "Dev",
"configurePreset": "Dev",

View File

@@ -1,5 +1,33 @@
cmake_minimum_required(VERSION 3.13)
# generic options.
option(ENABLE_NVJPG "" OFF)
option(ENABLE_NSZ "enables exporting to nsz" ON)
# lib options.
option(ENABLE_LIBUSBHSFS "enables FAT/exFAT hdd mounting" ON)
option(ENABLE_LIBUSBDVD "enables cd/dvd/iso/cue mounting" ON)
option(ENABLE_FTPSRV "enables MTP server support" ON)
option(ENABLE_LIBHAZE "enables MTP server support" ON)
# audio options.
option(ENABLE_AUDIO_MP3 "" ON)
option(ENABLE_AUDIO_OGG "" ON)
option(ENABLE_AUDIO_WAV "" ON)
option(ENABLE_AUDIO_FLAC "" ON)
# devoptab options.
option(ENABLE_DEVOPTAB_HTTP "" ON)
option(ENABLE_DEVOPTAB_NFS "" ON)
option(ENABLE_DEVOPTAB_SMB2 "" ON)
option(ENABLE_DEVOPTAB_FTP "" ON)
option(ENABLE_DEVOPTAB_WEBDAV "" ON)
# disable by default because we are CPU bound for upload/download.
# max speed is 8MiB/s, which is fine for wifi, but awful for ethernet.
# other clients get 36-40MiB/s.
# it also adds 230k to binary size, and i don't think anyone will use it.
option(ENABLE_DEVOPTAB_SFTP "" OFF)
set(sphaira_VERSION 0.13.3)
project(sphaira
@@ -46,8 +74,6 @@ add_executable(sphaira
source/ui/menus/themezer.cpp
source/ui/menus/ghdl.cpp
source/ui/menus/usb_menu.cpp
source/ui/menus/ftp_menu.cpp
source/ui/menus/mtp_menu.cpp
source/ui/menus/gc_menu.cpp
source/ui/menus/game_menu.cpp
source/ui/menus/game_meta_menu.cpp
@@ -85,16 +111,12 @@ add_executable(sphaira
source/web.cpp
source/hasher.cpp
source/i18n.cpp
source/ftpsrv_helper.cpp
source/haze_helper.cpp
source/threaded_file_transfer.cpp
source/title_info.cpp
source/minizip_helper.cpp
source/usbdvd.cpp
source/utils/utils.cpp
source/utils/audio.cpp
source/utils/nsz_dumper.cpp
source/utils/devoptab_common.cpp
source/utils/devoptab_romfs.cpp
source/utils/devoptab_save.cpp
@@ -104,11 +126,6 @@ add_executable(sphaira
source/utils/devoptab_xci.cpp
source/utils/devoptab_zip.cpp
source/utils/devoptab_bfsar.cpp
source/utils/devoptab_http.cpp
source/utils/devoptab_nfs.cpp
source/utils/devoptab_smb2.cpp
source/utils/devoptab_ftp.cpp
source/utils/devoptab_webdav.cpp
source/utils/devoptab_vfs.cpp
source/utils/devoptab_fatfs.cpp
@@ -188,17 +205,6 @@ target_compile_options(sphaira PRIVATE
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 85b3cf0
SOURCE_SUBDIR NONE
)
FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG f0b2a14
)
FetchContent_Declare(libpulsar
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
GIT_TAG ac7bc97
@@ -230,11 +236,6 @@ FetchContent_Declare(zstd
SOURCE_SUBDIR build/cmake
)
FetchContent_Declare(libusbhsfs
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
GIT_TAG 625269b
)
FetchContent_Declare(libnxtc
GIT_REPOSITORY https://github.com/ITotalJustice/libnxtc.git
GIT_TAG 88ce3d8
@@ -251,54 +252,286 @@ FetchContent_Declare(dr_libs
SOURCE_SUBDIR NONE
)
FetchContent_Declare(id3v2lib
GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git
GIT_TAG 141ffb8
)
if (ENABLE_NVJPG)
FetchContent_Declare(nvjpg
GIT_REPOSITORY https://github.com/ITotalJustice/oss-nvjpg.git
GIT_TAG 45680e7
)
FetchContent_Declare(libusbdvd
GIT_REPOSITORY https://github.com/proconsule/libusbdvd.git
GIT_TAG 3cb0613
)
FetchContent_MakeAvailable(nvjpg)
FetchContent_Declare(libnfs
GIT_REPOSITORY https://github.com/ITotalJustice/libnfs.git
GIT_TAG 65f3e11
)
add_library(nvjpg
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
${nvjpg_SOURCE_DIR}/lib/image.cpp
${nvjpg_SOURCE_DIR}/lib/surface.cpp
)
FetchContent_Declare(libsmb2
GIT_REPOSITORY https://github.com/ITotalJustice/libsmb2.git
GIT_TAG 867beea
)
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
FetchContent_Declare(pugixml
GIT_REPOSITORY https://github.com/zeux/pugixml.git
GIT_TAG v1.15
)
target_link_libraries(nvjpg PRIVATE nvjpg)
target_compile_definitions(sphaira PRIVATE ENABLE_NVJPG)
endif()
set(PUGIXML_NO_EXCEPTIONS ON)
# set(PUGIXML_NO_XPATH ON)
set(PUGIXML_WCHAR_MODE OFF)
if (ENABLE_NSZ)
target_sources(sphaira PRIVATE source/utils/nsz_dumper.cpp)
target_compile_definitions(sphaira PRIVATE ENABLE_NSZ)
endif()
set(USE_NEW_ZSTD ON)
# has issues with some homebrew and game icons (oxenfree, overwatch2).
set(USE_NVJPG OFF)
if (ENABLE_LIBUSBHSFS)
# enable this if you want ntfs and ext4 support, at the cost of a huge final binary size.
set(USBHSFS_GPL OFF)
set(USBHSFS_SXOS_DISABLE ON)
FetchContent_Declare(libusbhsfs
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
GIT_TAG 625269b
)
FetchContent_MakeAvailable(libusbhsfs)
target_compile_definitions(sphaira PRIVATE ENABLE_LIBUSBHSFS)
target_link_libraries(sphaira PRIVATE libusbhsfs)
else()
target_sources(sphaira PRIVATE source/ff16/ffunicode.c)
endif()
if (ENABLE_LIBUSBDVD)
FetchContent_Declare(libusbdvd
GIT_REPOSITORY https://github.com/proconsule/libusbdvd.git
GIT_TAG 3cb0613
)
FetchContent_MakeAvailable(libusbdvd)
add_library(libusbdvd
${libusbdvd_SOURCE_DIR}/source/usbdvd.cpp
${libusbdvd_SOURCE_DIR}/source/usbdvd_scsi.cpp
${libusbdvd_SOURCE_DIR}/source/usbdvd_utils.cpp
${libusbdvd_SOURCE_DIR}/source/fs/usbdvd_datadisc.cpp
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/audiocdfs.cpp
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/cdaudio_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/usbdvd_iso9660.cpp
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/iso9660_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/fs/udf/usbdvd_udf.cpp
${libusbdvd_SOURCE_DIR}/source/fs/udf/udf_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/os/switch/switch_usb.cpp
)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/os/switch)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/iso9660)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/udf)
target_include_directories(libusbdvd PUBLIC ${libusbdvd_SOURCE_DIR}/include)
target_compile_definitions(sphaira PRIVATE ENABLE_LIBUSBDVD)
target_link_libraries(sphaira PRIVATE libusbdvd)
target_sources(sphaira PRIVATE source/usbdvd.cpp)
endif()
if (ENABLE_FTPSRV)
FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 85b3cf0
SOURCE_SUBDIR NONE
)
FetchContent_MakeAvailable(ftpsrv)
set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
set(FTPSRV_LIB_PATH_SIZE 0x301)
set(FTPSRV_LIB_SESSIONS 16)
set(FTPSRV_LIB_BUF_SIZE 1024*64)
set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:FALSE>
USE_VFS_STORAGE=$<BOOL:TRUE>
# disabled as it may conflict with the gamecard menu.
USE_VFS_GC=$<BOOL:FALSE>
USE_VFS_USBHSFS=$<BOOL:${ENABLE_LIBUSBHSFS}>
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
# let sphaira handle init / closing of the hdd.
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
# disable romfs mounting as otherwise we cannot write / modify sphaira.nro
USE_VFS_ROMFS=$<BOOL:FALSE>
FTP_SOCKET_HEADER="${ftpsrv_SOURCE_DIR}/src/platform/nx/socket_nx.h"
)
add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
add_library(ftpsrv_helper
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
)
if (ENABLE_LIBUSBHSFS)
target_sources(ftpsrv_helper PRIVATE
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c
)
endif()
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv libusbhsfs)
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
target_compile_definitions(sphaira PRIVATE ENABLE_FTPSRV)
target_link_libraries(sphaira PRIVATE ftpsrv_helper)
target_sources(sphaira PRIVATE
source/ftpsrv_helper.cpp
source/ui/menus/ftp_menu.cpp
)
endif()
if (ENABLE_LIBHAZE)
FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG f0b2a14
)
FetchContent_MakeAvailable(libhaze)
target_compile_definitions(sphaira PRIVATE ENABLE_LIBHAZE)
target_link_libraries(sphaira PRIVATE libhaze)
target_sources(sphaira PRIVATE
source/haze_helper.cpp
source/ui/menus/mtp_menu.cpp
)
endif()
if (ENABLE_DEVOPTAB_HTTP)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_HTTP)
target_sources(sphaira PRIVATE source/utils/devoptab_http.cpp)
endif()
if (ENABLE_DEVOPTAB_NFS)
FetchContent_Declare(libnfs
GIT_REPOSITORY https://github.com/ITotalJustice/libnfs.git
GIT_TAG 65f3e11
)
FetchContent_MakeAvailable(libnfs)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_NFS)
target_link_libraries(sphaira PRIVATE nfs)
target_sources(sphaira PRIVATE source/utils/devoptab_nfs.cpp)
# todo: fix this upstream as nfs should export these folders.
target_include_directories(sphaira PRIVATE
${libnfs_SOURCE_DIR}/include
${libnfs_SOURCE_DIR}/include/nfsc
${libnfs_SOURCE_DIR}/nfs
)
endif()
if (ENABLE_DEVOPTAB_SMB2)
FetchContent_Declare(libsmb2
GIT_REPOSITORY https://github.com/ITotalJustice/libsmb2.git
GIT_TAG 867beea
)
FetchContent_MakeAvailable(libsmb2)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_SMB2)
target_link_libraries(sphaira PRIVATE smb2)
target_sources(sphaira PRIVATE source/utils/devoptab_smb2.cpp)
endif()
if (ENABLE_DEVOPTAB_FTP)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_FTP)
target_sources(sphaira PRIVATE source/utils/devoptab_ftp.cpp)
endif()
if (ENABLE_DEVOPTAB_SFTP)
# set to build from source, otherwise it will link against the older dkp libssh2.
if (1)
set(CRYPTO_BACKEND mbedTLS)
set(ENABLE_ZLIB_COMPRESSION ON)
set(ENABLE_DEBUG_LOGGING OFF)
set(BUILD_EXAMPLES OFF)
set(BUILD_TESTING OFF)
set(LINT OFF)
FetchContent_Declare(libssh2
GIT_REPOSITORY https://github.com/libssh2/libssh2.git
# GIT_TAG a0dafb3 # latest commit, works fine, but i'll stick to main release.
GIT_TAG libssh2-1.11.1
)
FetchContent_MakeAvailable(libssh2)
target_link_libraries(sphaira PRIVATE libssh2::libssh2)
else()
include(FindPkgConfig)
pkg_check_modules(LIBSSH2 libssh2 REQUIRED)
target_include_directories(sphaira PRIVATE ${LIBSSH2_INCLUDE_DIRS})
target_link_libraries(sphaira PRIVATE ${LIBSSH2_LIBRARIES})
endif()
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_SFTP)
target_sources(sphaira PRIVATE source/utils/devoptab_sftp.cpp)
endif()
if (ENABLE_DEVOPTAB_WEBDAV)
set(PUGIXML_NO_EXCEPTIONS ON)
set(PUGIXML_WCHAR_MODE OFF)
FetchContent_Declare(pugixml
GIT_REPOSITORY https://github.com/zeux/pugixml.git
GIT_TAG v1.15
)
FetchContent_MakeAvailable(pugixml)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_WEBDAV)
target_link_libraries(sphaira PRIVATE pugixml)
target_sources(sphaira PRIVATE source/utils/devoptab_webdav.cpp)
endif()
if (ENABLE_AUDIO_MP3)
FetchContent_Declare(id3v2lib
GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git
GIT_TAG 141ffb8
)
FetchContent_MakeAvailable(id3v2lib)
target_link_libraries(sphaira PRIVATE id3v2lib)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_MP3)
endif()
if (ENABLE_AUDIO_OGG)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_OGG)
endif()
if (ENABLE_AUDIO_WAV)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_WAV)
endif()
if (ENABLE_AUDIO_FLAC)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_FLAC)
endif()
# ztsd
set(ZSTD_BUILD_STATIC ON)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_COMPRESSION ON)
set(ZSTD_MULTITHREAD_SUPPORT ON)
set(ZSTD_BUILD_COMPRESSION ${ENABLE_NSZ})
set(ZSTD_MULTITHREAD_SUPPORT ${ENABLE_NSZ})
set(ZSTD_BUILD_DECOMPRESSION ON)
set(ZSTD_BUILD_DICTBUILDER OFF)
set(ZSTD_LEGACY_SUPPORT OFF)
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_TESTS OFF)
# minini
set(MININI_LIB_NAME minIni)
set(MININI_USE_STDIO ON)
set(MININI_USE_NX OFF)
set(MININI_USE_FLOAT ON)
# nanovg
if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(NANOVG_DEBUG ON)
endif()
@@ -312,6 +545,7 @@ set(NANOVG_NO_HDR ON)
set(NANOVG_NO_PIC ON)
set(NANOVG_NO_PNM ON)
# yyjson
set(YYJSON_INSTALL OFF)
set(YYJSON_DISABLE_READER OFF)
set(YYJSON_DISABLE_WRITER OFF)
@@ -321,66 +555,17 @@ set(YYJSON_DISABLE_NON_STANDARD ON)
set(YYJSON_DISABLE_UTF8_VALIDATION ON)
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
# enable this if you want ntfs and ext4 support, at the cost of a huge final binary size.
set(USBHSFS_GPL OFF)
set(USBHSFS_SXOS_DISABLE ON)
FetchContent_MakeAvailable(
ftpsrv
libhaze
libpulsar
nanovg
stb
minIni
yyjson
zstd
libusbhsfs
libnxtc
nvjpg
dr_libs
id3v2lib
libusbdvd
libnfs
libsmb2
pugixml
)
set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
set(FTPSRV_LIB_PATH_SIZE 0x301)
set(FTPSRV_LIB_SESSIONS 16)
set(FTPSRV_LIB_BUF_SIZE 1024*64)
set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:FALSE>
USE_VFS_STORAGE=$<BOOL:TRUE>
# disabled as it may conflict with the gamecard menu.
USE_VFS_GC=$<BOOL:FALSE>
USE_VFS_USBHSFS=$<BOOL:TRUE>
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
# let sphaira handle init / closing of the hdd.
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
# disable romfs mounting as otherwise we cannot write / modify sphaira.nro
USE_VFS_ROMFS=$<BOOL:FALSE>
FTP_SOCKET_HEADER="${ftpsrv_SOURCE_DIR}/src/platform/nx/socket_nx.h"
)
add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
add_library(ftpsrv_helper
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
)
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv libusbhsfs)
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
@@ -394,38 +579,6 @@ add_library(libnxtc
)
target_include_directories(libnxtc PUBLIC ${libnxtc_SOURCE_DIR}/include)
add_library(libusbdvd
${libusbdvd_SOURCE_DIR}/source/usbdvd.cpp
${libusbdvd_SOURCE_DIR}/source/usbdvd_scsi.cpp
${libusbdvd_SOURCE_DIR}/source/usbdvd_utils.cpp
${libusbdvd_SOURCE_DIR}/source/fs/usbdvd_datadisc.cpp
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/audiocdfs.cpp
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/cdaudio_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/usbdvd_iso9660.cpp
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/iso9660_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/fs/udf/usbdvd_udf.cpp
${libusbdvd_SOURCE_DIR}/source/fs/udf/udf_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/os/switch/switch_usb.cpp
)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/os/switch)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/iso9660)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/udf)
target_include_directories(libusbdvd PUBLIC ${libusbdvd_SOURCE_DIR}/include)
if (USE_NVJPG)
add_library(nvjpg
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
${nvjpg_SOURCE_DIR}/lib/image.cpp
${nvjpg_SOURCE_DIR}/lib/surface.cpp
)
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
endif()
find_package(ZLIB REQUIRED)
find_library(minizip_lib minizip REQUIRED)
find_path(minizip_inc minizip REQUIRED)
@@ -434,11 +587,6 @@ find_package(CURL REQUIRED)
find_path(mbedtls_inc mbedtls REQUIRED)
find_library(mbedcrypto_lib mbedcrypto REQUIRED)
if (NOT USE_NEW_ZSTD)
find_path(zstd_inc zstd.h REQUIRED)
find_library(zstd_lib zstd REQUIRED)
endif()
add_library(fatfs
source/ff16/diskio.c
source/ff16/ff.c
@@ -453,30 +601,16 @@ set_target_properties(sphaira PROPERTIES
CXX_EXTENSIONS ON
)
# todo: fix this upstream as nfs should export these folders.
target_include_directories(sphaira PRIVATE
${libnfs_SOURCE_DIR}/include
${libnfs_SOURCE_DIR}/include/nfsc
${libnfs_SOURCE_DIR}/nfs
)
target_link_libraries(sphaira PRIVATE
ftpsrv_helper
libhaze
libpulsar
minIni
nanovg
stb
yyjson
# libusbhsfs
libnxtc
fatfs
dr_libs
id3v2lib
libusbdvd
nfs
smb2
pugixml
libzstd_static
${minizip_lib}
ZLIB::ZLIB
@@ -484,20 +618,6 @@ target_link_libraries(sphaira PRIVATE
${mbedcrypto_lib}
)
if (USE_NEW_ZSTD)
message(STATUS "USING UPSTREAM ZSTD")
target_link_libraries(sphaira PRIVATE libzstd_static)
else()
message(STATUS "USING LOCAL ZSTD")
target_link_libraries(sphaira PRIVATE ${zstd_lib})
target_include_directories(sphaira PRIVATE ${zstd_inc})
endif()
if (USE_NVJPG)
target_link_libraries(sphaira PRIVATE nvjpg)
target_compile_definitions(sphaira PRIVATE USE_NVJPG)
endif()
target_include_directories(sphaira PRIVATE
include
${minizip_inc}

View File

@@ -12,6 +12,18 @@
namespace fs {
enum OpenMode : u32 {
OpenMode_Read = FsOpenMode_Read,
OpenMode_Write = FsOpenMode_Write,
OpenMode_Append = FsOpenMode_Append,
// enables buffering for stdio based files.
OpenMode_EnableBuffer = 1 << 16,
OpenMode_ReadBuffered = OpenMode_Read | OpenMode_EnableBuffer,
OpenMode_WriteBuffered = OpenMode_Write | OpenMode_EnableBuffer,
OpenMode_AppendBuffered = OpenMode_Append | OpenMode_EnableBuffer,
};
struct FsPath {
FsPath() = default;

View File

@@ -15,8 +15,6 @@ enum class Type {
Sha1,
Sha256,
Null,
Deflate,
Zstd,
};
struct BaseSource {

View File

@@ -23,6 +23,7 @@ Result MountVfsAll();
Result MountWebdavAll();
Result MountHttpAll();
Result MountFtpAll();
Result MountSftpAll();
Result MountNfsAll();
Result MountSmb2All();
Result MountFatfsAll();

View File

@@ -130,7 +130,7 @@ struct MountConfig {
std::string pass{};
std::string dump_path{};
std::optional<long> port{};
int timeout{3000}; // 3 seconds.
int timeout{5000}; // 5 seconds.
bool read_only{};
bool no_stat_file{true};
bool no_stat_dir{true};

View File

@@ -37,7 +37,10 @@
#include <ctime>
#include <span>
#include <dirent.h>
#include <usbhsfs.h>
#ifdef ENABLE_LIBUSBHSFS
#include <usbhsfs.h>
#endif // ENABLE_LIBUSBHSFS
extern "C" {
u32 __nx_applet_exit_mode = 0;
@@ -748,6 +751,7 @@ void App::SetNxlinkEnable(bool enable) {
void App::SetHddEnable(bool enable) {
if (App::GetHddEnable() != enable) {
g_app->m_hdd_enabled.Set(enable);
#ifdef ENABLE_LIBUSBHSFS
if (enable) {
if (App::GetWriteProtect()) {
usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
@@ -756,6 +760,7 @@ void App::SetHddEnable(bool enable) {
} else {
usbHsFsExit();
}
#endif // ENABLE_LIBUSBHSFS
}
}
@@ -763,11 +768,13 @@ void App::SetWriteProtect(bool enable) {
if (App::GetWriteProtect() != enable) {
g_app->m_hdd_write_protect.Set(enable);
#ifdef ENABLE_LIBUSBHSFS
if (enable) {
usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
} else {
usbHsFsSetFileSystemMountFlags(0);
}
#endif // ENABLE_LIBUSBHSFS
}
}
@@ -911,22 +918,28 @@ void App::Set12HourTimeEnable(bool enable) {
void App::SetMtpEnable(bool enable) {
if (App::GetMtpEnable() != enable) {
g_app->m_mtp_enabled.Set(enable);
#ifdef ENABLE_LIBHAZE
if (enable) {
haze::Init();
} else {
haze::Exit();
}
#endif // ENABLE_LIBHAZE
}
}
void App::SetFtpEnable(bool enable) {
if (App::GetFtpEnable() != enable) {
g_app->m_ftp_enabled.Set(enable);
#ifdef ENABLE_FTPSRV
if (enable) {
ftpsrv::Init();
} else {
ftpsrv::Exit();
}
#endif // ENABLE_FTPSRV
}
}
@@ -1554,21 +1567,26 @@ App::App(const char* argv0) {
}
}
#ifdef ENABLE_LIBHAZE
if (App::GetMtpEnable()) {
SCOPED_TIMESTAMP("mtp init");
haze::Init();
}
#endif // ENABLE_LIBHAZE
#ifdef ENABLE_FTPSRV
if (App::GetFtpEnable()) {
SCOPED_TIMESTAMP("ftp init");
ftpsrv::Init();
}
#endif // ENABLE_FTPSRV
if (App::GetNxlinkEnable()) {
SCOPED_TIMESTAMP("nxlink init");
nxlinkInitialize(nxlink_callback);
}
#ifdef ENABLE_LIBUSBHSFS
if (App::GetHddEnable()) {
SCOPED_TIMESTAMP("hdd init");
if (App::GetWriteProtect()) {
@@ -1577,13 +1595,16 @@ App::App(const char* argv0) {
usbHsFsInitialize(1);
}
#endif // ENABLE_LIBUSBHSFS
#ifdef ENABLE_LIBUSBDVD
{
SCOPED_TIMESTAMP("usbdvd init");
if (R_FAILED(usbdvd::MountAll())) {
log_write("[USBDVD] failed to mount\n");
}
}
#endif // ENABLE_LIBUSBDVD
{
SCOPED_TIMESTAMP("curl init");
@@ -1596,30 +1617,47 @@ App::App(const char* argv0) {
devoptab::MountVfsAll();
}
#ifdef ENABLE_DEVOPTAB_HTTP
{
SCOPED_TIMESTAMP("http init");
devoptab::MountHttpAll();
}
#endif // ENABLE_DEVOPTAB_HTTP
#ifdef ENABLE_DEVOPTAB_WEBDAV
{
SCOPED_TIMESTAMP("webdav init");
devoptab::MountWebdavAll();
}
#endif // ENABLE_DEVOPTAB_WEBDAV
#ifdef ENABLE_DEVOPTAB_FTP
{
SCOPED_TIMESTAMP("ftp init");
devoptab::MountFtpAll();
}
#endif // ENABLE_DEVOPTAB_FTP
#ifdef ENABLE_DEVOPTAB_SFTP
{
SCOPED_TIMESTAMP("sftp init");
devoptab::MountSftpAll();
}
#endif // ENABLE_DEVOPTAB_SFTP
#ifdef ENABLE_DEVOPTAB_NFS
{
SCOPED_TIMESTAMP("nfs init");
devoptab::MountNfsAll();
}
#endif // ENABLE_DEVOPTAB_NFS
#ifdef ENABLE_DEVOPTAB_SMB2
{
SCOPED_TIMESTAMP("smb init");
devoptab::MountSmb2All();
}
#endif // ENABLE_DEVOPTAB_SMB2
{
SCOPED_TIMESTAMP("fatfs init");
@@ -2186,8 +2224,10 @@ App::~App() {
// async exit as these threads sleep every 100ms.
{
SCOPED_TIMESTAMP("async signal");
nxlinkSignalExit();
#ifdef ENABLE_FTPSRV
ftpsrv::ExitSignal();
#endif // ENABLE_FTPSRV
nxlinkSignalExit();
audio::ExitSignal();
curl::ExitSignal();
}
@@ -2203,25 +2243,31 @@ App::~App() {
}
utils::Async async_exit([this](){
{
SCOPED_TIMESTAMP("usbdvd_exit");
usbdvd::UnmountAll();
}
{
SCOPED_TIMESTAMP("i18n_exit");
i18n::exit();
}
#ifdef ENABLE_LIBUSBDVD
{
SCOPED_TIMESTAMP("usbdvd_exit");
usbdvd::UnmountAll();
}
#endif // ENABLE_LIBUSBDVD
#ifdef ENABLE_LIBHAZE
{
SCOPED_TIMESTAMP("mtp exit");
haze::Exit();
}
#endif // ENABLE_LIBHAZE
#ifdef ENABLE_LIBUSBHSFS
{
SCOPED_TIMESTAMP("hdd exit");
usbHsFsExit();
}
#endif // ENABLE_LIBUSBHSFS
// this has to come before curl exit as it uses curl global.
{
@@ -2236,10 +2282,12 @@ App::~App() {
audio::Exit();
}
#ifdef ENABLE_FTPSRV
{
SCOPED_TIMESTAMP("ftp exit");
ftpsrv::Exit();
}
#endif // ENABLE_FTPSRV
{
SCOPED_TIMESTAMP("nxlink exit");

View File

@@ -7,7 +7,6 @@
#include "i18n.hpp"
#include "location.hpp"
#include "threaded_file_transfer.hpp"
#include "haze_helper.hpp"
#include "ui/sidebar.hpp"
#include "ui/error_box.hpp"
@@ -133,9 +132,9 @@ struct WriteNullSource final : WriteSource {
struct WriteUsbSource final : WriteSource {
WriteUsbSource(u64 transfer_timeout) {
// disable mtp if enabled.
m_was_mtp_enabled = haze::IsInit();
m_was_mtp_enabled = App::GetMtpEnable();
if (m_was_mtp_enabled) {
haze::Exit();
App::SetMtpEnable(false);
}
m_usb = std::make_unique<usb::dump::Usb>(transfer_timeout);
@@ -145,7 +144,7 @@ struct WriteUsbSource final : WriteSource {
m_usb.reset();
if (m_was_mtp_enabled) {
haze::Init();
App::SetMtpEnable(true);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -468,6 +468,10 @@ bool DirExists(const FsPath& path) {
}
Result OpenFile(fs::Fs* fs, const FsPathReal& path, u32 mode, File* f) {
const auto should_buffer = (mode & OpenMode_EnableBuffer);
// remove the invalid flag so that native fs doesn't error.
mode &= ~OpenMode_EnableBuffer;
f->m_fs = fs;
f->m_mode = mode;
@@ -486,6 +490,13 @@ Result OpenFile(fs::Fs* fs, const FsPathReal& path, u32 mode, File* f) {
}
R_UNLESS(f->m_stdio, Result_FsUnknownStdioError);
// disable buffering to match native fs behavior.
// this also causes problems with network io as it will do double reads.
// which kills performance (see sftp).
if (!should_buffer) {
std::setvbuf(f->m_stdio, nullptr, _IONBF, 0);
}
}
R_SUCCEED();

View File

@@ -4,9 +4,6 @@
#include <mbedtls/md5.h>
#include <utility>
#include <zlib.h>
#include <zstd.h>
namespace sphaira::hash {
namespace {
@@ -81,131 +78,6 @@ private:
size_t m_in_size{};
};
// this currently crashes when freeing the pool :/
#define USE_THREAD_POOL 0
struct HashZstd final : HashSource {
HashZstd() {
const auto num_threads = 3;
const auto level = ZSTD_CLEVEL_DEFAULT;
m_ctx = ZSTD_createCCtx();
if (!m_ctx) {
log_write("[ZSTD] failed to create ctx\n");
}
#if USE_THREAD_POOL
m_pool = ZSTD_createThreadPool(num_threads);
if (!m_pool) {
log_write("[ZSTD] failed to create pool\n");
}
if (ZSTD_isError(ZSTD_CCtx_refThreadPool(m_ctx, m_pool))) {
log_write("[ZSTD] failed ZSTD_CCtx_refThreadPool(m_pool)\n");
}
#endif
if (ZSTD_isError(ZSTD_CCtx_setParameter(m_ctx, ZSTD_c_compressionLevel, level))) {
log_write("[ZSTD] failed ZSTD_CCtx_setParameter(ZSTD_c_compressionLevel)\n");
}
if (ZSTD_isError(ZSTD_CCtx_setParameter(m_ctx, ZSTD_c_nbWorkers, num_threads))) {
log_write("[ZSTD] failed ZSTD_CCtx_setParameter(ZSTD_c_nbWorkers)\n");
}
m_out_buf.resize(ZSTD_CStreamOutSize());
}
~HashZstd() {
ZSTD_freeCCtx(m_ctx);
#if USE_THREAD_POOL
// crashes here during ZSTD_pthread_join()
// ZSTD_freeThreadPool(m_pool);
#endif
}
void Update(const void* buf, s64 size, s64 file_size) override {
ZSTD_inBuffer input = { buf, (u64)size, 0 };
const auto last_chunk = m_in_size + size >= file_size;
const auto mode = last_chunk ? ZSTD_e_end : ZSTD_e_continue;
while (input.pos < input.size) {
ZSTD_outBuffer output = { m_out_buf.data(), m_out_buf.size(), 0 };
const size_t remaining = ZSTD_compressStream2(m_ctx, &output , &input, mode);
if (ZSTD_isError(remaining)) {
log_write("[ZSTD] error: %zu\n", remaining);
break;
}
m_out_size += output.pos;
};
m_in_size += size;
}
void Get(std::string& out) override {
log_write("getting size: %zu vs %zu\n", m_out_size, m_in_size);
char str[64];
const u32 percentage = ((double)m_out_size / (double)m_in_size) * 100.0;
std::snprintf(str, sizeof(str), "%u%%", percentage);
out = str;
log_write("got size: %zu vs %zu\n", m_out_size, m_in_size);
}
private:
ZSTD_CCtx* m_ctx{};
ZSTD_threadPool* m_pool{};
std::vector<u8> m_out_buf{};
size_t m_in_size{};
size_t m_out_size{};
};
struct HashDeflate final : HashSource {
HashDeflate() {
deflateInit(&m_ctx, Z_DEFAULT_COMPRESSION);
m_out_buf.resize(deflateBound(&m_ctx, 1024*1024*16)); // max chunk size.
}
~HashDeflate() {
deflateEnd(&m_ctx);
}
void Update(const void* buf, s64 size, s64 file_size) override {
m_ctx.avail_in = size;
m_ctx.next_in = const_cast<Bytef*>((const Bytef*)buf);
const auto last_chunk = m_in_size + size >= file_size;
const auto mode = last_chunk ? Z_FINISH : Z_NO_FLUSH;
while (m_ctx.avail_in != 0) {
m_ctx.next_out = m_out_buf.data();
m_ctx.avail_out = m_out_buf.size();
const auto rc = deflate(&m_ctx, mode);
if (Z_OK != rc) {
if (Z_STREAM_END != rc) {
log_write("[ZLIB] deflate error: %d\n", rc);
}
break;
}
}
m_in_size += size;
}
void Get(std::string& out) override {
char str[64];
const u32 percentage = ((double)m_ctx.total_out / (double)m_in_size) * 100.0;
std::snprintf(str, sizeof(str), "%u%%", percentage);
out = str;
}
private:
z_stream m_ctx{};
std::vector<u8> m_out_buf{};
size_t m_in_size{};
};
struct HashCrc32 final : HashSource {
void Update(const void* buf, s64 size, s64 file_size) override {
m_seed = crc32CalculateWithSeed(m_seed, buf, size);
@@ -328,8 +200,6 @@ auto GetTypeStr(Type type) -> const char* {
case Type::Sha1: return "SHA1";
case Type::Sha256: return "SHA256";
case Type::Null: return "/dev/null (Speed Test)";
case Type::Deflate: return "Deflate (Speed Test)";
case Type::Zstd: return "ZSTD (Speed Test)";
}
return "";
}
@@ -341,8 +211,6 @@ Result Hash(ui::ProgressBox* pbox, Type type, BaseSource* source, std::string& o
case Type::Sha1: return Hash(pbox, std::make_unique<HashSha1>(), source, out);
case Type::Sha256: return Hash(pbox, std::make_unique<HashSha256>(), source, out);
case Type::Null: return Hash(pbox, std::make_unique<HashNull>(), source, out);
case Type::Deflate: return Hash(pbox, std::make_unique<HashDeflate>(), source, out);
case Type::Zstd: return Hash(pbox, std::make_unique<HashZstd>(), source, out);
}
std::unreachable();
}

View File

@@ -1,11 +1,17 @@
#include "location.hpp"
#include "fs.hpp"
#include "app.hpp"
#include "usbdvd.hpp"
#include "utils/devoptab.hpp"
#include <cstring>
#include <usbhsfs.h>
#ifdef ENABLE_LIBUSBDVD
#include "usbdvd.hpp"
#endif // ENABLE_LIBUSBDVD
#ifdef ENABLE_LIBUSBHSFS
#include <usbhsfs.h>
#endif // ENABLE_LIBUSBHSFS
namespace sphaira::location {
namespace {
@@ -38,6 +44,7 @@ auto GetStdio(bool write) -> StdioEntries {
}
}
#ifdef ENABLE_LIBUSBDVD
// try and load usbdvd entry.
// todo: check if more than 1 entry is supported.
// todo: only call if usbdvd is init.
@@ -47,7 +54,9 @@ auto GetStdio(bool write) -> StdioEntries {
out.emplace_back(entry);
}
}
#endif // ENABLE_LIBUSBDVD
#ifdef ENABLE_LIBUSBHSFS
// bail out early if usbhdd is disabled.
if (!App::GetHddEnable()) {
log_write("[USBHSFS] not enabled\n");
@@ -78,6 +87,7 @@ auto GetStdio(bool write) -> StdioEntries {
out.emplace_back(e.name, display_name, flags);
log_write("\t[USBHSFS] %s name: %s serial: %s man: %s\n", e.name, e.product_name, e.serial_number, e.manufacturer);
}
#endif // ENABLE_LIBUSBHSFS
return out;
}

View File

@@ -372,7 +372,7 @@ void FileFuncNative(zlib_filefunc64_def* funcs) {
Result PeekFirstFileName(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& name) {
fs::File file;
R_TRY(fs->OpenFile(path, FsOpenMode_Read, &file));
R_TRY(fs->OpenFile(path, fs::OpenMode_ReadBuffered, &file));
mmz_LocalHeader local_hdr;
u64 bytes_read;

View File

@@ -31,7 +31,6 @@
#include "yati/yati.hpp"
#include "yati/source/file.hpp"
#include <usbdvd.h>
#include <minIni.h>
#include <minizip/zip.h>
#include <minizip/unzip.h>
@@ -45,6 +44,10 @@
#include <utility>
#include <ranges>
#ifdef ENABLE_LIBUSBDVD
#include <usbdvd.h>
#endif // ENABLE_LIBUSBDVD
namespace sphaira::ui::menu::filebrowser {
namespace {
@@ -724,7 +727,9 @@ void FsView::OnClick() {
App::Push<music::Menu>(GetFs(), GetNewPathCurrent());
} else if (IsExtension(entry.GetExtension(), IMAGE_EXTENSIONS)) {
App::Push<imageview::Menu>(GetFs(), GetNewPathCurrent());
} else if (IsExtension(entry.GetExtension(), CDDVD_EXTENSIONS)) {
}
#ifdef ENABLE_LIBUSBDVD
else if (IsExtension(entry.GetExtension(), CDDVD_EXTENSIONS)) {
std::shared_ptr<CUSBDVD> usbdvd;
if (entry.GetExtension() == "cue") {
@@ -748,7 +753,9 @@ void FsView::OnClick() {
} else {
log_write("[USBDVD] failed to mount\n");
}
} else if (IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) {
}
#endif // ENABLE_LIBUSBDVD
else if (IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) {
InstallFiles();
} else if (IsSd()) {
const auto assoc_list = m_menu->FindFileAssocFor();
@@ -1895,12 +1902,6 @@ void FsView::DisplayAdvancedOptions() {
options->Add<SidebarEntryCallback>("/dev/null (Speed Test)"_i18n, [this](){
DisplayHash(hash::Type::Null);
});
options->Add<SidebarEntryCallback>("Deflate (Speed Test)"_i18n, [this](){
DisplayHash(hash::Type::Deflate);
});
options->Add<SidebarEntryCallback>("ZSTD (Speed Test)"_i18n, [this](){
DisplayHash(hash::Type::Zstd);
});
});
}

View File

@@ -110,6 +110,7 @@ private:
bool m_is_file_based_emummc{};
};
#ifdef ENABLE_NSZ
Result NszExport(ProgressBox* pbox, const keys::Keys& keys, dump::BaseSource* _source, dump::WriteSource* writer, const fs::FsPath& path) {
auto source = (NspSource*)_source;
@@ -145,6 +146,7 @@ Result NszExport(ProgressBox* pbox, const keys::Keys& keys, dump::BaseSource* _s
R_SUCCEED();
}
#endif // ENABLE_NSZ
Result Notify(Result rc, const std::string& error_message) {
if (R_FAILED(rc)) {
@@ -979,6 +981,7 @@ void DumpNsp(const std::vector<NspEntry>& entries, bool to_nsz) {
auto source = std::make_shared<NspSource>(entries);
if (to_nsz) {
#ifdef ENABLE_NSZ
// todo: log keys error.
keys::Keys keys;
keys::parse_keys(keys, true);
@@ -986,6 +989,7 @@ void DumpNsp(const std::vector<NspEntry>& entries, bool to_nsz) {
dump::Dump(source, paths, [keys](ProgressBox* pbox, dump::BaseSource* source, dump::WriteSource* writer, const fs::FsPath& path) {
return NszExport(pbox, keys, source, writer, path);
});
#endif // ENABLE_NSZ
} else {
dump::Dump(source, paths);
}

View File

@@ -323,6 +323,7 @@ private:
const s64 m_offset;
};
#ifdef ENABLE_NSZ
Result NszExport(ProgressBox* pbox, const keys::Keys& keys, dump::BaseSource* _source, dump::WriteSource* writer, const fs::FsPath& path) {
auto source = (XciSource*)_source;
@@ -464,6 +465,7 @@ Result NszExport(ProgressBox* pbox, const keys::Keys& keys, dump::BaseSource* _s
R_SUCCEED();
}
#endif // ENABLE_NSZ
struct GcSource final : yati::source::Base {
GcSource(const ApplicationEntry& entry, fs::FsNativeGameCard* fs);
@@ -617,7 +619,9 @@ Menu::Menu(u32 flags) : MenuBase{"GameCard"_i18n, flags} {
add("Export Certificate"_i18n, DumpFileFlag_Cert);
add("Export Initial Data"_i18n, DumpFileFlag_Initial);
} else if (m_option_index == 2) {
#ifdef ENABLE_NSZ
DumpXcz(0);
#endif // ENABLE_NSZ
} else if (m_option_index == 3) {
const auto rc = MountGcFs();
App::PushErrorBox(rc, "Failed to mount GameCard filesystem"_i18n);
@@ -729,6 +733,11 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
if (!m_mounted) {
colour = ThemeEntryID_TEXT_INFO;
}
if (i == 2) {
#ifndef ENABLE_NSZ
colour = ThemeEntryID_TEXT_INFO;
#endif // ENABLE_NSZ
}
gfx::drawTextArgs(vg, x + 15, y + (h / 2.f), 23.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour), "%s", i18n::get(g_option_list[i]).c_str());
});
@@ -1099,6 +1108,7 @@ void Menu::OnChangeIndex(s64 new_index) {
}
}
#ifdef ENABLE_NSZ
Result Menu::DumpXcz(u32 flags) {
R_TRY(GcMountStorage());
@@ -1122,6 +1132,7 @@ Result Menu::DumpXcz(u32 flags) {
R_SUCCEED();
}
#endif // ENABLE_NSZ
Result Menu::DumpGames(u32 flags) {
// first, try and mount the storage.
@@ -1183,18 +1194,8 @@ Result Menu::DumpGames(u32 flags) {
paths.emplace_back(BuildFullDumpPath(DumpFileType_Initial, m_entries));
}
if (0) {
// todo: log keys error.
keys::Keys keys;
keys::parse_keys(keys, true);
dump::Dump(source, paths, [keys](ProgressBox* pbox, dump::BaseSource* source, dump::WriteSource* writer, const fs::FsPath& path) {
return NszExport(pbox, keys, source, writer, path);
});
} else {
dump::Dump(source, paths, nullptr, location_flags);
}
dump::Dump(source, paths, nullptr, location_flags);
R_SUCCEED();
};

View File

@@ -78,15 +78,19 @@ const MiscMenuEntry MISC_MENU_ENTRIES[] = {
"Download releases directly from GitHub. "\
"Custom entries can be added to /config/sphaira/github" },
#ifdef ENABLE_FTPSRV
{ .name = "FTP", .title = "FTP Install", .func = MiscMenuFuncGenerator<ui::menu::ftp::Menu>, .flag = MiscMenuFlag_Install, .info =
"Install apps via FTP.\n\n"\
"NOTE: This feature does not always work, use at your own risk. "\
"If you encounter an issue, do not open an issue, it will not be fixed." },
#endif // ENABLE_FTPSRV
#ifdef ENABLE_LIBHAZE
{ .name = "MTP", .title = "MTP Install", .func = MiscMenuFuncGenerator<ui::menu::mtp::Menu>, .flag = MiscMenuFlag_Install, .info =
"Install apps via MTP.\n\n"\
"NOTE: This feature does not always work, use at your own risk. "\
"If you encounter an issue, do not open an issue, it will not be fixed." },
#endif // ENABLE_LIBHAZE
{ .name = "USB", .title = "USB Install", .func = MiscMenuFuncGenerator<ui::menu::usb::Menu>, .flag = MiscMenuFlag_Install, .info =
"Install apps via USB.\n\n"\

View File

@@ -5,7 +5,6 @@
#include "log.hpp"
#include "ui/nvg_util.hpp"
#include "i18n.hpp"
#include "haze_helper.hpp"
#include "utils/thread.hpp"
@@ -35,10 +34,10 @@ Menu::Menu(u32 flags) : MenuBase{"USB"_i18n, flags} {
}});
// if mtp is enabled, disable it for now.
m_was_mtp_enabled = haze::IsInit();
m_was_mtp_enabled = App::GetMtpEnable();
if (m_was_mtp_enabled) {
App::Notify("Disable MTP for usb install"_i18n);
haze::Exit();
App::SetMtpEnable(false);
}
// 3 second timeout for transfers.
@@ -70,7 +69,7 @@ Menu::~Menu() {
if (m_was_mtp_enabled) {
App::Notify("Re-enabled MTP"_i18n);
haze::Init();
App::SetMtpEnable(true);
}
}

View File

@@ -21,52 +21,55 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wunused-function"
#if 1
#define DR_FLAC_IMPLEMENTATION
#define DR_FLAC_NO_OGG
#define DR_FLAC_NO_STDIO
#define DRFLAC_API static
#define DRFLAC_PRIVATE static
#include <dr_flac.h>
#endif
#define DR_WAV_IMPLEMENTATION
#define DR_WAV_NO_STDIO
#define DRWAV_API static
#define DRWAV_PRIVATE static
#include <dr_wav.h>
#define DR_MP3_IMPLEMENTATION
#define DR_MP3_NO_STDIO
#define DRMP3_API static
#define DRMP3_PRIVATE static
// improves load / seek times.
// hopefully drmp3 will have binary seek rather than linear.
// this also improves
#define DRMP3_DATA_CHUNK_SIZE (1024*64)
#include <dr_mp3.h>
#if 0
#define DR_VORBIS_IMPLEMENTATION
#define DR_VORBIS_NO_STDIO
#define DR_VORBIS_API static
#include "dr_vorbis.h"
#endif
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Walloca"
#pragma GCC diagnostic ignored "-Wunused-variable"
#define STB_VORBIS_NO_PUSHDATA_API
#define STB_VORBIS_NO_STDIO
#define STB_VORBIS_NO_OPENMEM
#include "stb_vorbis.h"
#pragma GCC diagnostic pop
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#include <id3v2lib.h>
#ifdef ENABLE_AUDIO_FLAC
#define DR_FLAC_IMPLEMENTATION
#define DR_FLAC_NO_OGG
#define DR_FLAC_NO_STDIO
#define DRFLAC_API static
#define DRFLAC_PRIVATE static
#include <dr_flac.h>
#endif // ENABLE_AUDIO_FLAC
#ifdef ENABLE_AUDIO_WAV
#define DR_WAV_IMPLEMENTATION
#define DR_WAV_NO_STDIO
#define DRWAV_API static
#define DRWAV_PRIVATE static
#include <dr_wav.h>
#endif // ENABLE_AUDIO_WAV
#ifdef ENABLE_AUDIO_MP3
#define DR_MP3_IMPLEMENTATION
#define DR_MP3_NO_STDIO
#define DRMP3_API static
#define DRMP3_PRIVATE static
// improves load / seek times.
// hopefully drmp3 will have binary seek rather than linear.
// this also improves
#define DRMP3_DATA_CHUNK_SIZE (1024*64)
#include <dr_mp3.h>
#include <id3v2lib.h>
#endif // ENABLE_AUDIO_MP3
#ifdef ENABLE_AUDIO_OGG
#if 0
#define DR_VORBIS_IMPLEMENTATION
#define DR_VORBIS_NO_STDIO
#define DR_VORBIS_API static
#include "dr_vorbis.h"
#endif
#define STB_VORBIS_NO_PUSHDATA_API
#define STB_VORBIS_NO_STDIO
#define STB_VORBIS_NO_OPENMEM
#include "stb_vorbis.h"
#endif // ENABLE_AUDIO_OGG
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#include <pulsar.h>
@@ -130,6 +133,7 @@ private:
s64 m_size{};
};
#ifdef ENABLE_AUDIO_MP3
// gta vice "encrypted" mp3's using xor 0x22, very cool.
struct GTAViceCityFile final : File {
size_t ReadFile(void* _buf, size_t read_size) override {
@@ -164,6 +168,7 @@ auto convert_utf16(const ID3v2_TextFrameData* data) -> std::string{
buf[sz] = 0;
return buf;
}
#endif // ENABLE_AUDIO_MP3
struct Base {
virtual ~Base() = default;
@@ -458,6 +463,7 @@ struct PlsrBFWAV final : PlsrBase {
}
};
#ifdef ENABLE_AUDIO_WAV
struct DrWAV final : CustomBase {
~DrWAV() {
drwav_uninit(&m_wav);
@@ -532,7 +538,9 @@ private:
drwav m_wav{};
File m_file{};
};
#endif // ENABLE_AUDIO_WAV
#ifdef ENABLE_AUDIO_MP3
struct DrMP3 final : CustomBase {
DrMP3(std::unique_ptr<File>&& file = std::make_unique<File>()) : m_file{std::forward<decltype(file)>(file)} {
@@ -668,8 +676,9 @@ private:
std::vector<drmp3_seek_point> m_seek_points{};
#endif
};
#endif // ENABLE_AUDIO_MP3
#if 1
#ifdef ENABLE_AUDIO_FLAC
struct DrFLAC final : CustomBase {
~DrFLAC() {
drflac_close(m_flac);
@@ -718,8 +727,9 @@ private:
drflac* m_flac{};
File m_file{};
};
#endif
#endif // ENABLE_AUDIO_FLAC
#ifdef ENABLE_AUDIO_OGG
// api is not ready, leaving this here for when it is.
#if 0
struct DrOGG final : CustomBase {
@@ -864,6 +874,7 @@ private:
stb_vorbis* m_ogg{};
File m_file{};
};
#endif // ENABLE_AUDIO_OGG
constexpr u32 MAX_SONGS = 4;
@@ -1072,24 +1083,32 @@ Result OpenSong(fs::Fs* fs, const fs::FsPath& path, u32 flags, SongID* id) {
else if (path.ends_with(".bfwav")) {
source = std::make_unique<PlsrBFWAV>();
}
#ifdef ENABLE_AUDIO_WAV
else if (path.ends_with(".wav")) {
source = std::make_unique<DrWAV>();
}
#endif // ENABLE_AUDIO_WAV
#ifdef ENABLE_AUDIO_MP3
else if (path.ends_with(".mp3") || path.ends_with(".mp2") || path.ends_with(".mp1")) {
source = std::make_unique<DrMP3>();
}
else if (path.ends_with(".adf")) {
source = std::make_unique<DrMP3>(std::make_unique<GTAViceCityFile>());
}
#endif // ENABLE_AUDIO_MP3
#ifdef ENABLE_AUDIO_FLAC
else if (path.ends_with(".flac")) {
source = std::make_unique<DrFLAC>();
}
#endif // ENABLE_AUDIO_FLAC
#ifdef ENABLE_AUDIO_OGG
// else if (path.ends_with(".ogg")) {
// source = std::make_unique<DrOGG>();
// }
else if (path.ends_with(".ogg")) {
source = std::make_unique<stbOGG>();
}
#endif // ENABLE_AUDIO_OGG
R_UNLESS(source, 0x1);
R_TRY(source->LoadFile(fs, path, flags));

View File

@@ -1475,7 +1475,7 @@ void UmountAllNeworkDevices() {
continue;
}
log_write("[DEVOPTAB] Unmounting %s\n", entry->device.config.url.c_str());
log_write("[DEVOPTAB] Unmounting %s URL: %s\n", entry->mount.s, entry->device.config.url.c_str());
entry.reset();
}
}
@@ -1488,7 +1488,7 @@ void UmountNeworkDevice(const fs::FsPath& mount) {
});
if (it != g_entries.end()) {
log_write("[DEVOPTAB] Unmounting %s\n", (*it)->device.config.url.c_str());
log_write("[DEVOPTAB] Unmounting %s URL: %s\n", (*it)->mount.s, (*it)->device.config.url.c_str());
it->reset();
} else {
log_write("[DEVOPTAB] No such mount %s\n", mount.s);

View File

@@ -460,4 +460,15 @@ Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) {
return fat.buffered->Read2(dst, offset, size);
}
// libusbhsfs also defines these, so only define if not using it.
#ifndef ENABLE_LIBUSBHSFS
void* ff_memalloc (UINT msize) {
return std::malloc(msize);
}
void ff_memfree (void* mblock) {
std::free(mblock);
}
#endif // ENABLE_LIBUSBHSFS
} // extern "C"

View File

@@ -1,3 +1,5 @@
#ifdef ENABLE_DEVOPTAB_NFS
#include "utils/devoptab_common.hpp"
#include "defines.hpp"
#include "log.hpp"
@@ -412,3 +414,17 @@ Result MountNfsAll() {
}
} // namespace sphaira::devoptab
#else // ENABLE_DEVOPTAB_NFS
#include "defines.hpp"
namespace sphaira::devoptab {
Result MountNfsAll() {
R_SUCCEED();
}
} // namespace sphaira::devoptab
#endif // ENABLE_DEVOPTAB_NFS

View File

@@ -0,0 +1,540 @@
// NOTE (09/09/2025): do not use as it is unusably slow, even on local network.
// the issue isn't the ssh protocol (although it is slow). haven't looked into libssh2 yet
// it could be how they handle blocking. CPU usage is 0%, so its not that.
// NOTE (09/09/2025): its just reads that as super slow, which is even more strange!
// writes are very fast (for sftp), maxing switch wifi. what is going on???
// NOTE (09/09/2025): the issue was that fread was buffering, causing double reads.
// it would read the first 4mb, then read another 1kb.
// disabling buffering fixed the issue, and i have disabled buffering by default.
// buffering is now enabled only when requested.
#include "utils/devoptab_common.hpp"
#include "utils/profile.hpp"
#include "defines.hpp"
#include "log.hpp"
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <string>
#include <cstring>
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
namespace sphaira::devoptab {
namespace {
struct Device final : common::MountDevice {
using MountDevice::MountDevice;
~Device();
private:
bool Mount() override;
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
ssize_t devoptab_write(void *fd, const char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_unlink(const char *path) override;
int devoptab_rename(const char *oldName, const char *newName) override;
int devoptab_mkdir(const char *path, int mode) override;
int devoptab_rmdir(const char *path) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
int devoptab_ftruncate(void *fd, off_t len) override;
int devoptab_statvfs(const char *path, struct statvfs *buf) override;
int devoptab_fsync(void *fd) override;
private:
LIBSSH2_SESSION* m_session{};
LIBSSH2_SFTP* m_sftp_session{};
int m_socket{};
bool m_is_ssh2_init{}; // set if libssh2_init() was successful.
bool m_is_handshake_done{}; // set if handshake was successful.
bool m_is_auth_done{}; // set if auth was successful.
bool mounted{};
};
struct File {
LIBSSH2_SFTP_HANDLE* fd{};
};
struct Dir {
LIBSSH2_SFTP_HANDLE* fd{};
};
int convert_flags_to_sftp(int flags) {
int sftp_flags = 0;
if ((flags & O_ACCMODE) == O_RDONLY) {
sftp_flags |= LIBSSH2_FXF_READ;
} else if ((flags & O_ACCMODE) == O_WRONLY) {
sftp_flags |= LIBSSH2_FXF_WRITE;
} else if ((flags & O_ACCMODE) == O_RDWR) {
sftp_flags |= LIBSSH2_FXF_READ | LIBSSH2_FXF_WRITE;
}
if (flags & O_CREAT) {
sftp_flags |= LIBSSH2_FXF_CREAT;
}
if (flags & O_TRUNC) {
sftp_flags |= LIBSSH2_FXF_TRUNC;
}
if (flags & O_APPEND) {
sftp_flags |= LIBSSH2_FXF_APPEND;
}
if (flags & O_EXCL) {
sftp_flags |= LIBSSH2_FXF_EXCL;
}
return sftp_flags;
}
int convert_mode_to_sftp(int mode) {
int sftp_mode = 0;
// permission bits.
sftp_mode |= (mode & S_IRUSR) ? LIBSSH2_SFTP_S_IRUSR : 0;
sftp_mode |= (mode & S_IWUSR) ? LIBSSH2_SFTP_S_IWUSR : 0;
sftp_mode |= (mode & S_IXUSR) ? LIBSSH2_SFTP_S_IXUSR : 0;
sftp_mode |= (mode & S_IRGRP) ? LIBSSH2_SFTP_S_IRGRP : 0;
sftp_mode |= (mode & S_IWGRP) ? LIBSSH2_SFTP_S_IWGRP : 0;
sftp_mode |= (mode & S_IXGRP) ? LIBSSH2_SFTP_S_IXGRP : 0;
sftp_mode |= (mode & S_IROTH) ? LIBSSH2_SFTP_S_IROTH : 0;
sftp_mode |= (mode & S_IWOTH) ? LIBSSH2_SFTP_S_IWOTH : 0;
sftp_mode |= (mode & S_IXOTH) ? LIBSSH2_SFTP_S_IXOTH : 0;
// file type bits.
if (S_ISREG(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFREG;
} else if (S_ISDIR(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFDIR;
} else if (S_ISCHR(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFCHR;
} else if (S_ISBLK(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFBLK;
} else if (S_ISFIFO(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFIFO;
} else if (S_ISLNK(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFLNK;
} else if (S_ISSOCK(mode)) {
sftp_mode |= LIBSSH2_SFTP_S_IFSOCK;
}
return sftp_mode;
}
void fill_stat(struct stat* st, const LIBSSH2_SFTP_ATTRIBUTES* attrs) {
if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
st->st_mode = attrs->permissions;
}
if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) {
st->st_size = attrs->filesize;
}
if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) {
st->st_uid = attrs->uid;
st->st_gid = attrs->gid;
}
if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) {
st->st_atime = attrs->atime;
st->st_mtime = attrs->mtime;
st->st_ctime = attrs->mtime; // no ctime available, use mtime.
}
st->st_nlink = 1;
}
Device::~Device() {
if (m_sftp_session) {
libssh2_sftp_shutdown(m_sftp_session);
}
if (m_session) {
libssh2_session_disconnect(m_session, "Normal Shutdown");
libssh2_session_free(m_session);
}
if (m_socket > 0) {
shutdown(m_socket, SHUT_RDWR);
close(m_socket);
}
if (m_is_ssh2_init) {
libssh2_exit();
}
}
bool Device::Mount() {
if (mounted) {
return true;
}
log_write("[SFTP] Mounting %s version: %s\n", this->config.url.c_str(), LIBSSH2_VERSION);
if (!m_socket) {
// connect the socket.
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
addrinfo* res{};
const auto port = this->config.port.value_or(22);
const auto port_str = std::to_string(port);
auto ret = getaddrinfo(this->config.url.c_str(), port_str.c_str(), &hints, &res);
if (ret != 0) {
log_write("[SFTP] getaddrinfo() failed: %s\n", gai_strerror(ret));
return false;
}
ON_SCOPE_EXIT(freeaddrinfo(res));
for (auto addr = res; addr != nullptr; addr = addr->ai_next) {
m_socket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (m_socket < 0) {
log_write("[SFTP] socket() failed: %s\n", std::strerror(errno));
continue;
}
ret = connect(m_socket, addr->ai_addr, addr->ai_addrlen);
if (ret < 0) {
log_write("[SFTP] connect() failed: %s\n", std::strerror(errno));
close(m_socket);
m_socket = -1;
continue;
}
break;
}
if (m_socket < 0) {
log_write("[SFTP] Failed to connect to %s:%ld\n", this->config.url.c_str(), port);
return false;
}
log_write("[SFTP] Connected to %s:%ld\n", this->config.url.c_str(), port);
}
if (!m_is_ssh2_init) {
auto ret = libssh2_init(0);
if (ret != 0) {
log_write("[SFTP] libssh2_init() failed: %d\n", ret);
return false;
}
m_is_ssh2_init = true;
}
if (!m_session) {
m_session = libssh2_session_init();
if (!m_session) {
log_write("[SFTP] libssh2_session_init() failed\n");
return false;
}
libssh2_session_set_blocking(m_session, 1);
libssh2_session_flag(m_session, LIBSSH2_FLAG_COMPRESS, 1);
libssh2_session_set_timeout(m_session, this->config.timeout);
// dkp libssh2 is too old for this.
#if LIBSSH2_VERSION_NUM >= 0x010B00
libssh2_session_set_read_timeout(m_session, this->config.timeout);
#endif
}
if (this->config.user.empty() || this->config.pass.empty()) {
log_write("[SFTP] Missing username or password\n");
return false;
}
if (!m_is_handshake_done) {
const auto ret = libssh2_session_handshake(m_session, m_socket);
if (ret) {
log_write("[SFTP] libssh2_session_handshake() failed: %d\n", ret);
return false;
}
m_is_handshake_done = true;
}
if (!m_is_auth_done) {
const auto userauthlist = libssh2_userauth_list(m_session, this->config.user.c_str(), this->config.user.length());
if (!userauthlist) {
log_write("[SFTP] libssh2_userauth_list() failed\n");
return false;
}
// just handle user/pass auth for now, pub/priv key is a bit overkill.
if (std::strstr(userauthlist, "password")) {
auto ret = libssh2_userauth_password(m_session, this->config.user.c_str(), this->config.pass.c_str());
if (ret) {
log_write("[SFTP] Password auth failed: %d\n", ret);
return false;
}
} else {
log_write("[SFTP] No supported auth methods found\n");
return false;
}
m_is_auth_done = true;
}
if (!m_sftp_session) {
m_sftp_session = libssh2_sftp_init(m_session);
if (!m_sftp_session) {
log_write("[SFTP] libssh2_sftp_init() failed\n");
return false;
}
}
log_write("[SFTP] Mounted %s\n", this->config.url.c_str());
return mounted = true;
}
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct);
file->fd = libssh2_sftp_open(m_sftp_session, path, convert_flags_to_sftp(flags), convert_mode_to_sftp(mode));
if (!file->fd) {
log_write("[SFTP] libssh2_sftp_open() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd);
libssh2_sftp_close(file->fd);
return 0;
}
ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd);
// enable if sftp read is slow again.
#if 0
char name[256]{};
std::snprintf(name, sizeof(name), "SFTP read %zu bytes", len);
SCOPED_TIMESTAMP(name);
#endif
const auto ret = libssh2_sftp_read(file->fd, ptr, len);
if (ret < 0) {
log_write("[SFTP] libssh2_sftp_read() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return ret;
}
ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) {
auto file = static_cast<File*>(fd);
const auto ret = libssh2_sftp_write(file->fd, ptr, len);
if (ret < 0) {
log_write("[SFTP] libssh2_sftp_write() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return ret;
}
off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd);
const auto current_pos = libssh2_sftp_tell64(file->fd);
if (dir == SEEK_CUR) {
pos += current_pos;
} else if (dir == SEEK_END) {
LIBSSH2_SFTP_ATTRIBUTES attrs{};
auto ret = libssh2_sftp_fstat(file->fd, &attrs);
if (ret || !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE)) {
log_write("[SFTP] libssh2_sftp_fstat() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
} else {
pos = attrs.filesize;
}
}
// libssh2 already does this internally, but handle just in case this changes.
if (pos == current_pos) {
return pos;
}
log_write("[SFTP] Seeking to %ld dir: %d old: %llu\n", pos, dir, current_pos);
libssh2_sftp_seek64(file->fd, pos);
return libssh2_sftp_tell64(file->fd);
}
int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd);
LIBSSH2_SFTP_ATTRIBUTES attrs{};
const auto ret = libssh2_sftp_fstat(file->fd, &attrs);
if (ret) {
log_write("[SFTP] libssh2_sftp_fstat() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
fill_stat(st, &attrs);
return 0;
}
int Device::devoptab_unlink(const char *path) {
const auto ret = libssh2_sftp_unlink(m_sftp_session, path);
if (ret) {
log_write("[SFTP] libssh2_sftp_unlink() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
int Device::devoptab_rename(const char *oldName, const char *newName) {
const auto ret = libssh2_sftp_rename(m_sftp_session, oldName, newName);
if (ret) {
log_write("[SFTP] libssh2_sftp_rename() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
int Device::devoptab_mkdir(const char *path, int mode) {
const auto ret = libssh2_sftp_mkdir(m_sftp_session, path, mode);
if (ret) {
log_write("[SFTP] libssh2_sftp_mkdir() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
int Device::devoptab_rmdir(const char *path) {
const auto ret = libssh2_sftp_rmdir(m_sftp_session, path);
if (ret) {
log_write("[SFTP] libssh2_sftp_rmdir() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd);
dir->fd = libssh2_sftp_opendir(m_sftp_session, path);
if (!dir->fd) {
log_write("[SFTP] libssh2_sftp_opendir() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
libssh2_sftp_rewind(dir->fd);
return 0;
}
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
LIBSSH2_SFTP_ATTRIBUTES attrs{};
const auto ret = libssh2_sftp_readdir(dir->fd, filename, NAME_MAX, &attrs);
if (ret <= 0) {
return -ENOENT;
}
fill_stat(filestat, &attrs);
return 0;
}
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
libssh2_sftp_closedir(dir->fd);
return 0;
}
int Device::devoptab_lstat(const char *path, struct stat *st) {
LIBSSH2_SFTP_ATTRIBUTES attrs{};
const auto ret = libssh2_sftp_stat(m_sftp_session, path, &attrs);
if (ret) {
log_write("[SFTP] libssh2_sftp_stat() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
fill_stat(st, &attrs);
return 0;
}
#if 1
int Device::devoptab_ftruncate(void *fd, off_t len) {
// stubbed.
return 0;
}
#endif
int Device::devoptab_statvfs(const char *path, struct statvfs *buf) {
LIBSSH2_SFTP_STATVFS sftp_st{};
const auto ret = libssh2_sftp_statvfs(m_sftp_session, path, std::strlen(path), &sftp_st);
if (ret) {
log_write("[SFTP] libssh2_sftp_statvfs() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
buf->f_bsize = sftp_st.f_bsize;
buf->f_frsize = sftp_st.f_frsize;
buf->f_blocks = sftp_st.f_blocks;
buf->f_bfree = sftp_st.f_bfree;
buf->f_bavail = sftp_st.f_bavail;
buf->f_files = sftp_st.f_files;
buf->f_ffree = sftp_st.f_ffree;
buf->f_favail = sftp_st.f_favail;
buf->f_fsid = sftp_st.f_fsid;
buf->f_flag = sftp_st.f_flag;
buf->f_namemax = sftp_st.f_namemax;
return 0;
}
int Device::devoptab_fsync(void *fd) {
auto file = static_cast<File*>(fd);
const auto ret = libssh2_sftp_fsync(file->fd);
if (ret) {
log_write("[SFTP] libssh2_sftp_fsync() failed: %ld\n", libssh2_sftp_last_error(m_sftp_session));
return -EIO;
}
return 0;
}
} // namespace
Result MountSftpAll() {
return common::MountNetworkDevice([](const common::MountConfig& cfg) {
return std::make_unique<Device>(cfg);
},
sizeof(File), sizeof(Dir),
"SFTP"
);
}
} // namespace sphaira::devoptab