add mtp (haze) ftp (ftpsrv), update RA file assoc, nxlink now polls for connection.
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/2048_libretro_libnx.nro
|
||||
supported_extensions=
|
||||
database=2048
|
||||
4
assets/romfs/assoc/DoubleCherryGB_libretro_libnx.ini
Normal file
4
assets/romfs/assoc/DoubleCherryGB_libretro_libnx.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/DoubleCherryGB_libretro_libnx.nro
|
||||
supported_extensions=cgb|dmg|gb|gbc|sgb
|
||||
database=Nintendo - Game Boy|Nintendo - Game Boy Color
|
||||
4
assets/romfs/assoc/ardens_libretro_libnx.ini
Normal file
4
assets/romfs/assoc/ardens_libretro_libnx.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/ardens_libretro_libnx.nro
|
||||
supported_extensions=hex|arduboy
|
||||
database=Arduboy Inc - Arduboy
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/arduous_libretro_libnx.nro
|
||||
supported_extensions=hex
|
||||
database=Arduboy
|
||||
database=Arduboy Inc - Arduboy
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/atari800_libretro_libnx.nro
|
||||
supported_extensions=xfd|atr|cdm|cas|bin|a52|zip|atx|car|rom|com|xex
|
||||
supported_extensions=xfd|atr|dcm|cas|bin|a52|zip|atx|car|rom|com|xex|m3u
|
||||
database=Atari - 5200|Atari - 8-bit
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/bluemsx_libretro_libnx.nro
|
||||
supported_extensions=rom|ri|mx1|mx2|col|dsk|cas|sg|sc|sf|m3u
|
||||
database=Microsoft - MSX|Microsoft - MSX2|Coleco - ColecoVision|Sega - SG-1000
|
||||
supported_extensions=rom|ri|mx1|mx2|dsk|col|sg|sc|sf|cas|m3u
|
||||
database=Microsoft - MSX|Microsoft - MSX2|Coleco - ColecoVision|Sega - SG-1000|Spectravideo - SVI-318 - SVI-328
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/citra_libretro_libnx.nro
|
||||
supported_extensions=3ds|3dsx|elf|axf|cci|cxi|app
|
||||
database=Nintendo - Nintendo 3DS
|
||||
4
assets/romfs/assoc/dosbox_pure_libretro_libnx.ini
Normal file
4
assets/romfs/assoc/dosbox_pure_libretro_libnx.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/dosbox_pure_libretro_libnx.nro
|
||||
supported_extensions=zip|dosz|exe|com|bat|iso|chd|cue|ins|img|ima|vhd|jrc|tc|m3u|m3u8|conf|/
|
||||
database=DOS
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/dosbox_svn_libretro_libnx.nro
|
||||
supported_extensions=exe|com|bat|conf|cue|iso
|
||||
supported_extensions=exe|com|bat|conf|cue|iso|img|/
|
||||
database=DOS
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/fbalpha2012_cps1_libretro_libnx.nro
|
||||
supported_extensions=zip
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/fbalpha2012_cps2_libretro_libnx.nro
|
||||
supported_extensions=zip
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/fbalpha2012_libretro_libnx.nro
|
||||
supported_extensions=iso|zip|7z
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/fbalpha2012_neogeo_libretro_libnx.nro
|
||||
supported_extensions=zip
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/frodo_libretro_libnx.nro
|
||||
supported_extensions=d64|t64|x64|p00|lnx|zip
|
||||
supported_extensions=d64|t64|x64|p00|lnx|lyx|zip
|
||||
database=Commodore - 64
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/fuse_libretro_libnx.nro
|
||||
supported_extensions=tzx|tap|z80|rzx|scl|trd|dsk|zip
|
||||
supported_extensions=tzx|tap|z80|rzx|scl|trd|dsk|dck|sna|szx|zip
|
||||
database=Sinclair - ZX Spectrum +3|Sinclair - ZX Spectrum
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/gme_libretro_libnx.nro
|
||||
supported_extensions=ay|gbs|gym|hes|kss|nsf|nsfe|sap|spc|vgm|vgz|zip
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/gong_libretro_libnx.nro
|
||||
supported_extensions=
|
||||
4
assets/romfs/assoc/gpsp_libretro_libnx.ini
Normal file
4
assets/romfs/assoc/gpsp_libretro_libnx.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/gpsp_libretro_libnx.nro
|
||||
supported_extensions=gba|bin
|
||||
database=Nintendo - Game Boy Advance
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/handy_libretro_libnx.nro
|
||||
supported_extensions=lnx|o
|
||||
supported_extensions=lnx|lyx|o
|
||||
database=Atari - Lynx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/mame2000_libretro_libnx.nro
|
||||
supported_extensions=zip|7z|chd
|
||||
supported_extensions=zip|7z
|
||||
database=MAME 2000
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/mednafen_lynx_libretro_libnx.nro
|
||||
supported_extensions=lnx|o
|
||||
supported_extensions=lnx|lyx|o
|
||||
database=Atari - Lynx
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/minivmac_libretro_libnx.nro
|
||||
supported_extensions=dsk|img|zip|hvf|cmd
|
||||
4
assets/romfs/assoc/mrboom_libretro_libnx.ini
Normal file
4
assets/romfs/assoc/mrboom_libretro_libnx.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/mrboom_libretro_libnx.nro
|
||||
supported_extensions=desktop
|
||||
database=MrBoom
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/mu_libretro_libnx.nro
|
||||
supported_extensions=prc|pqa|img|pdb|zip
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/numero_libretro_libnx.nro
|
||||
supported_extensions=8xp|8xk|8xg
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/pcsx_rearmed_libretro_libnx.nro
|
||||
supported_extensions=bin|cue|img|mdf|pbp|toc|cbn|m3u|ccd|chd
|
||||
supported_extensions=bin|cue|img|mdf|pbp|toc|cbn|m3u|ccd|chd|iso|exe
|
||||
database=Sony - PlayStation
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/pocketcdg_libretro_libnx.nro
|
||||
supported_extensions=cdg
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/ppsspp_libretro_libnx.nro
|
||||
supported_extensions=elf|iso|cso|prx|pbp
|
||||
supported_extensions=elf|iso|cso|prx|pbp|chd
|
||||
database=Sony - PlayStation Portable
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/px68k_libretro_libnx.nro
|
||||
supported_extensions=dim|zip|img|d88|88d|hdm|dup|2hd|xdf|hdf|cmd|m3u
|
||||
supported_extensions=dim|img|d88|88d|hdm|dup|2hd|xdf|hdf|cmd|m3u
|
||||
database=Sharp - X68000
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/quasi88_libretro_libnx.nro
|
||||
supported_extensions=d88|u88|m3u
|
||||
database=NEC - PC-8001 - PC-8801
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/retro8_libretro_libnx.nro
|
||||
supported_extensions=p8|png
|
||||
database=PICO8
|
||||
database=PICO-8
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/stella_libretro_libnx.nro
|
||||
path=/retroarch/cores/stella2023_libretro_libnx.nro
|
||||
supported_extensions=a26|bin
|
||||
database=Atari - 2600
|
||||
@@ -1,3 +0,0 @@
|
||||
[config]
|
||||
path=/retroarch/cores/superbroswar_libretro_libnx.nro
|
||||
supported_extensions=game
|
||||
4
assets/romfs/assoc/vircon32_libretro_libnx.ini
Normal file
4
assets/romfs/assoc/vircon32_libretro_libnx.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/vircon32_libretro_libnx.nro
|
||||
supported_extensions=v32|V32
|
||||
database=Vircon32
|
||||
@@ -1,4 +1,4 @@
|
||||
[config]
|
||||
path=/retroarch/cores/x1_libretro_libnx.nro
|
||||
supported_extensions=dx1|zip|2d|2hd|tfd|d88|88d|hdm|xdf|dup|tap|cmd
|
||||
database=Sharp X1
|
||||
database=Sharp - X1
|
||||
|
||||
@@ -72,6 +72,7 @@ add_executable(sphaira
|
||||
source/swkbd.cpp
|
||||
source/web.cpp
|
||||
source/i18n.cpp
|
||||
source/ftpsrv_helper.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE
|
||||
@@ -82,6 +83,16 @@ target_compile_definitions(sphaira PRIVATE
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_QUIET FALSE)
|
||||
|
||||
FetchContent_Declare(ftpsrv
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
|
||||
GIT_TAG 8d5a14e
|
||||
)
|
||||
|
||||
FetchContent_Declare(libhaze
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
|
||||
GIT_TAG 3244b9e
|
||||
)
|
||||
|
||||
FetchContent_Declare(libpulsar
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
|
||||
GIT_TAG d729be3
|
||||
@@ -135,7 +146,16 @@ set(YYJSON_DISABLE_NON_STANDARD ON)
|
||||
set(YYJSON_DISABLE_UTF8_VALIDATION ON)
|
||||
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
|
||||
|
||||
set(FTPSRV_LIB_BUILD TRUE)
|
||||
set(FTPSRV_LIB_SOCK_UNISTD TRUE)
|
||||
set(FTPSRV_LIB_VFS_CUSTOM ${CMAKE_CURRENT_SOURCE_DIR}/include/ftpsrv_helper.hpp)
|
||||
set(FTPSRV_LIB_PATH_SIZE 0x301)
|
||||
set(FTPSRV_LIB_SESSIONS 32)
|
||||
set(FTPSRV_LIB_BUF_SIZE 1024*64)
|
||||
|
||||
FetchContent_MakeAvailable(
|
||||
ftpsrv
|
||||
libhaze
|
||||
libpulsar
|
||||
nanovg
|
||||
stb
|
||||
@@ -143,6 +163,31 @@ FetchContent_MakeAvailable(
|
||||
yyjson
|
||||
)
|
||||
|
||||
# todo: upstream cmake
|
||||
add_library(libhaze
|
||||
${libhaze_SOURCE_DIR}/source/async_usb_server.cpp
|
||||
${libhaze_SOURCE_DIR}/source/device_properties.cpp
|
||||
${libhaze_SOURCE_DIR}/source/event_reactor.cpp
|
||||
${libhaze_SOURCE_DIR}/source/haze.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_object_database.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_object_heap.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder_android_operations.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder_mtp_operations.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder_ptp_operations.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder.cpp
|
||||
${libhaze_SOURCE_DIR}/source/usb_session.cpp
|
||||
)
|
||||
target_include_directories(libhaze PUBLIC ${libhaze_SOURCE_DIR}/include)
|
||||
set_target_properties(libhaze PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_EXTENSIONS ON
|
||||
CXX_STANDARD 20
|
||||
CXX_EXTENSIONS ON
|
||||
# force optimisations in debug mode as otherwise vapor errors
|
||||
# due to force_inline attribute failing...
|
||||
COMPILE_OPTIONS "$<$<CONFIG:Debug>:-Os>"
|
||||
)
|
||||
|
||||
# todo: upstream cmake
|
||||
add_library(libpulsar
|
||||
${libpulsar_SOURCE_DIR}/src/archive/archive_file.c
|
||||
@@ -195,6 +240,8 @@ set_target_properties(sphaira PROPERTIES
|
||||
)
|
||||
|
||||
target_link_libraries(sphaira PRIVATE
|
||||
ftpsrv
|
||||
libhaze
|
||||
libpulsar
|
||||
minIni-sphaira
|
||||
nanovg
|
||||
|
||||
@@ -46,11 +46,12 @@ public:
|
||||
static auto GetVg() -> NVGcontext*;
|
||||
static void Push(std::shared_ptr<ui::Widget>);
|
||||
|
||||
// this is thread safe (todo: make it thread safe)
|
||||
// this is thread safe
|
||||
static void Notify(std::string text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void Notify(ui::NotifEntry entry);
|
||||
static void NotifyPop(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void NotifyClear(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void NotifyFlashLed();
|
||||
|
||||
static auto GetThemeMetaList() -> std::span<ThemeMeta>;
|
||||
static void SetTheme(u64 theme_index);
|
||||
@@ -61,6 +62,8 @@ public:
|
||||
// returns true if we are hbmenu.
|
||||
static auto IsHbmenu() -> bool;
|
||||
|
||||
static auto GetMtpEnable() -> bool;
|
||||
static auto GetFtpEnable() -> bool;
|
||||
static auto GetNxlinkEnable() -> bool;
|
||||
static auto GetLogEnable() -> bool;
|
||||
static auto GetReplaceHbmenuEnable() -> bool;
|
||||
@@ -71,6 +74,8 @@ public:
|
||||
static auto GetThemeMusicEnable() -> bool;
|
||||
static auto GetLanguage() -> long;
|
||||
|
||||
static void SetMtpEnable(bool enable);
|
||||
static void SetFtpEnable(bool enable);
|
||||
static void SetNxlinkEnable(bool enable);
|
||||
static void SetLogEnable(bool enable);
|
||||
static void SetReplaceHbmenuEnable(bool enable);
|
||||
@@ -138,6 +143,8 @@ public:
|
||||
bool m_quit{};
|
||||
|
||||
option::OptionBool m_nxlink_enabled{INI_SECTION, "nxlink_enabled", true};
|
||||
option::OptionBool m_mtp_enabled{INI_SECTION, "mtp_enabled", false};
|
||||
option::OptionBool m_ftp_enabled{INI_SECTION, "ftp_enabled", false};
|
||||
option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false};
|
||||
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
|
||||
option::OptionBool m_install{INI_SECTION, "install", false};
|
||||
|
||||
@@ -224,18 +224,18 @@ enum SvcError {
|
||||
};
|
||||
|
||||
enum FsError {
|
||||
FsError_ResultPathNotFound = 0x202,
|
||||
FsError_ResultPathAlreadyExists = 0x402,
|
||||
FsError_ResultTargetLocked = 0xE02,
|
||||
FsError_PathNotFound = 0x202,
|
||||
FsError_PathAlreadyExists = 0x402,
|
||||
FsError_TargetLocked = 0xE02,
|
||||
FsError_UsableSpaceNotEnoughMmcCalibration = 0x4602,
|
||||
FsError_UsableSpaceNotEnoughMmcSafe = 0x4802,
|
||||
FsError_UsableSpaceNotEnoughMmcUser = 0x4A02,
|
||||
FsError_UsableSpaceNotEnoughMmcSystem = 0x4C02,
|
||||
FsError_ResultUsableSpaceNotEnoughSdCard = 0x4E02,
|
||||
FsError_ResultUnsupportedSdkVersion = 0x6402,
|
||||
FsError_ResultMountNameAlreadyExists = 0x7802,
|
||||
FsError_ResultPartitionNotFound = 0x7D202,
|
||||
FsError_ResultTargetNotFound = 0x7D402,
|
||||
FsError_UsableSpaceNotEnoughSdCard = 0x4E02,
|
||||
FsError_UnsupportedSdkVersion = 0x6402,
|
||||
FsError_MountNameAlreadyExists = 0x7802,
|
||||
FsError_PartitionNotFound = 0x7D202,
|
||||
FsError_TargetNotFound = 0x7D402,
|
||||
FsError_PortSdCardNoDevice = 0xFA202,
|
||||
FsError_GameCardCardNotInserted = 0x13B002,
|
||||
FsError_GameCardCardNotActivated = 0x13B402,
|
||||
@@ -286,9 +286,9 @@ enum FsError {
|
||||
FsError_GameCardFsCheckHandleInGetStatusFailure = 0x171402,
|
||||
FsError_GameCardFsCheckHandleInCreateReadOnlyFailure = 0x172002,
|
||||
FsError_GameCardFsCheckHandleInCreateSecureReadOnlyFailure = 0x172202,
|
||||
FsError_ResultNotImplemented = 0x177202,
|
||||
FsError_ResultAlreadyExists = 0x177602,
|
||||
FsError_ResultOutOfRange = 0x177A02,
|
||||
FsError_NotImplemented = 0x177202,
|
||||
FsError_AlreadyExists = 0x177602,
|
||||
FsError_OutOfRange = 0x177A02,
|
||||
FsError_AllocationMemoryFailedInFatFileSystemA = 0x190202,
|
||||
FsError_AllocationMemoryFailedInFatFileSystemB = 0x190402,
|
||||
FsError_AllocationMemoryFailedInFatFileSystemC = 0x190602,
|
||||
@@ -348,18 +348,18 @@ enum FsError {
|
||||
FsError_FatFsFormatIllegalSectorsC = 0x280C02,
|
||||
FsError_FatFsFormatIllegalSectorsD = 0x280E02,
|
||||
FsError_UnexpectedInMountTableA = 0x296A02,
|
||||
FsError_ResultTooLongPath = 0x2EE602,
|
||||
FsError_ResultInvalidCharacter = 0x2EE802,
|
||||
FsError_ResultInvalidPathFormat = 0x2EEA02,
|
||||
FsError_ResultDirectoryUnobtainable = 0x2EEC02,
|
||||
FsError_ResultInvalidOffset = 0x2F5A02,
|
||||
FsError_ResultInvalidSize = 0x2F5C02,
|
||||
FsError_ResultNullptrArgument = 0x2F5E02,
|
||||
FsError_ResultInvalidAlignment = 0x2F6002,
|
||||
FsError_ResultInvalidMountName = 0x2F6202,
|
||||
FsError_ResultExtensionSizeTooLarge = 0x2F6402,
|
||||
FsError_ResultExtensionSizeInvalid = 0x2F6602,
|
||||
FsError_ResultFileExtensionWithoutOpenModeAllowAppend = 0x307202,
|
||||
FsError_TooLongPath = 0x2EE602,
|
||||
FsError_InvalidCharacter = 0x2EE802,
|
||||
FsError_InvalidPathFormat = 0x2EEA02,
|
||||
FsError_DirectoryUnobtainable = 0x2EEC02,
|
||||
FsError_InvalidOffset = 0x2F5A02,
|
||||
FsError_InvalidSize = 0x2F5C02,
|
||||
FsError_NullptrArgument = 0x2F5E02,
|
||||
FsError_InvalidAlignment = 0x2F6002,
|
||||
FsError_InvalidMountName = 0x2F6202,
|
||||
FsError_ExtensionSizeTooLarge = 0x2F6402,
|
||||
FsError_ExtensionSizeInvalid = 0x2F6602,
|
||||
FsError_FileExtensionWithoutOpenModeAllowAppend = 0x307202,
|
||||
FsError_UnsupportedCommitTarget = 0x313A02,
|
||||
FsError_UnsupportedSetSizeForNotResizableSubStorage = 0x313C02,
|
||||
FsError_UnsupportedSetSizeForResizableSubStorage = 0x313E02,
|
||||
@@ -444,14 +444,14 @@ enum FsError {
|
||||
FsError_UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem = 0x31E002,
|
||||
FsError_UnsupportedWriteForZeroBitmapHashStorageFile = 0x31E202,
|
||||
FsError_UnsupportedSetSizeForZeroBitmapHashStorageFile = 0x31E402,
|
||||
FsError_ResultNcaExternalKeyUnregisteredDeprecated = 0x326602,
|
||||
FsError_ResultFileNotClosed = 0x326E02,
|
||||
FsError_ResultDirectoryNotClosed = 0x327002,
|
||||
FsError_ResultWriteModeFileNotClosed = 0x327202,
|
||||
FsError_ResultAllocatorAlreadyRegistered = 0x327402,
|
||||
FsError_ResultDefaultAllocatorAlreadyUsed = 0x327602,
|
||||
FsError_ResultAllocatorAlignmentViolation = 0x327A02,
|
||||
FsError_ResultUserNotExist = 0x328202,
|
||||
FsError_NcaExternalKeyUnregisteredDeprecated = 0x326602,
|
||||
FsError_FileNotClosed = 0x326E02,
|
||||
FsError_DirectoryNotClosed = 0x327002,
|
||||
FsError_WriteModeFileNotClosed = 0x327202,
|
||||
FsError_AllocatorAlreadyRegistered = 0x327402,
|
||||
FsError_DefaultAllocatorAlreadyUsed = 0x327602,
|
||||
FsError_AllocatorAlignmentViolation = 0x327A02,
|
||||
FsError_UserNotExist = 0x328202,
|
||||
FsError_FileNotFound = 0x339402,
|
||||
FsError_DirectoryNotFound = 0x339602,
|
||||
FsError_MappingTableFull = 0x346402,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <nxlink.h>
|
||||
#include <haze.h>
|
||||
#include "download.hpp"
|
||||
|
||||
namespace sphaira::evman {
|
||||
@@ -23,6 +24,7 @@ struct ExitEventData {
|
||||
using EventData = std::variant<
|
||||
LaunchNroEventData,
|
||||
ExitEventData,
|
||||
HazeCallbackData,
|
||||
NxlinkCallbackData,
|
||||
DownloadEventData
|
||||
>;
|
||||
|
||||
33
sphaira/include/ftpsrv_helper.hpp
Normal file
33
sphaira/include/ftpsrv_helper.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
struct FtpVfsFile {
|
||||
FsFile fd;
|
||||
s64 off;
|
||||
s64 buf_off;
|
||||
s64 buf_size;
|
||||
bool is_write;
|
||||
bool is_valid;
|
||||
u8 buf[1024 * 1024 * 1];
|
||||
};
|
||||
|
||||
struct FtpVfsDir {
|
||||
FsDir dir;
|
||||
bool is_valid;
|
||||
};
|
||||
|
||||
struct FtpVfsDirEntry {
|
||||
FsDirectoryEntry buf;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
namespace sphaira::ftpsrv {
|
||||
|
||||
bool Init();
|
||||
void Exit();
|
||||
|
||||
} // namespace sphaira::ftpsrv
|
||||
|
||||
#endif // __cplusplus
|
||||
@@ -110,19 +110,18 @@ private:
|
||||
void LoadAssocEntriesPath(const fs::FsPath& path);
|
||||
void LoadAssocEntries();
|
||||
auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
|
||||
void OnIndexChange();
|
||||
|
||||
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, entry.name);
|
||||
};
|
||||
}
|
||||
|
||||
auto GetNewPath(u64 index) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, GetEntry(index).name);
|
||||
};
|
||||
}
|
||||
|
||||
auto GetNewPathCurrent() const -> fs::FsPath {
|
||||
return GetNewPath(m_index);
|
||||
};
|
||||
}
|
||||
|
||||
auto GetSelectedEntries() const -> std::vector<FileEntry> {
|
||||
if (!m_selected_count) {
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
#include "fs.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "ftpsrv_helper.hpp"
|
||||
|
||||
#include <nanovg_dk.h>
|
||||
#include <minIni.h>
|
||||
#include <pulsar.h>
|
||||
#include <haze.h>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
@@ -200,7 +202,13 @@ auto GetNroIcon(const std::vector<u8>& nro_icon) -> std::vector<u8> {
|
||||
return nro_icon;
|
||||
}
|
||||
|
||||
void haze_callback(const HazeCallbackData *data) {
|
||||
App::NotifyFlashLed();
|
||||
evman::push(*data, false);
|
||||
}
|
||||
|
||||
void nxlink_callback(const NxlinkCallbackData *data) {
|
||||
App::NotifyFlashLed();
|
||||
evman::push(*data, false);
|
||||
}
|
||||
|
||||
@@ -247,6 +255,9 @@ void App::Loop() {
|
||||
} else if constexpr(std::is_same_v<T, evman::ExitEventData>) {
|
||||
log_write("[ExitEventData] got event\n");
|
||||
m_quit = true;
|
||||
} else if constexpr(std::is_same_v<T, HazeCallbackData>) {
|
||||
// log_write("[ExitEventData] got event\n");
|
||||
// m_quit = true;
|
||||
} else if constexpr(std::is_same_v<T, NxlinkCallbackData>) {
|
||||
switch (arg.type) {
|
||||
case NxlinkCallbackType_Connected:
|
||||
@@ -330,6 +341,28 @@ void App::NotifyClear(ui::NotifEntry::Side side) {
|
||||
g_app->m_notif_manager.Clear(side);
|
||||
}
|
||||
|
||||
void App::NotifyFlashLed() {
|
||||
static const HidsysNotificationLedPattern pattern = {
|
||||
.baseMiniCycleDuration = 0x1, // 12.5ms.
|
||||
.totalMiniCycles = 0x1, // 1 mini cycle(s).
|
||||
.totalFullCycles = 0x1, // 1 full run(s).
|
||||
.startIntensity = 0xF, // 100%.
|
||||
.miniCycles = {{
|
||||
.ledIntensity = 0xF, // 100%.
|
||||
.transitionSteps = 0xF, // 1 step(s). Total 12.5ms.
|
||||
.finalStepDuration = 0xF, // Forced 12.5ms.
|
||||
}}
|
||||
};
|
||||
|
||||
s32 total;
|
||||
HidsysUniquePadId unique_pad_ids[16] = {0};
|
||||
if (R_SUCCEEDED(hidsysGetUniquePadIds(unique_pad_ids, 16, &total))) {
|
||||
for (int i = 0; i < total; i++) {
|
||||
hidsysSetNotificationLedPattern(&pattern, unique_pad_ids[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto App::GetThemeMetaList() -> std::span<ThemeMeta> {
|
||||
return g_app->m_theme_meta_entries;
|
||||
}
|
||||
@@ -383,6 +416,14 @@ auto App::GetThemeMusicEnable() -> bool {
|
||||
return g_app->m_theme_music.Get();
|
||||
}
|
||||
|
||||
auto App::GetMtpEnable() -> bool {
|
||||
return g_app->m_mtp_enabled.Get();
|
||||
}
|
||||
|
||||
auto App::GetFtpEnable() -> bool {
|
||||
return g_app->m_ftp_enabled.Get();
|
||||
}
|
||||
|
||||
auto App::GetLanguage() -> long {
|
||||
return g_app->m_language.Get();
|
||||
}
|
||||
@@ -434,6 +475,28 @@ void App::SetThemeMusicEnable(bool enable) {
|
||||
PlaySoundEffect(SoundEffect::SoundEffect_Music);
|
||||
}
|
||||
|
||||
void App::SetMtpEnable(bool enable) {
|
||||
if (App::GetMtpEnable() != enable) {
|
||||
g_app->m_mtp_enabled.Set(enable);
|
||||
if (enable) {
|
||||
hazeInitialize(haze_callback);
|
||||
} else {
|
||||
hazeExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetFtpEnable(bool enable) {
|
||||
if (App::GetFtpEnable() != enable) {
|
||||
g_app->m_ftp_enabled.Set(enable);
|
||||
if (enable) {
|
||||
ftpsrv::Init();
|
||||
} else {
|
||||
ftpsrv::Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetLanguage(long index) {
|
||||
if (App::GetLanguage() != index) {
|
||||
g_app->m_language.Set(index);
|
||||
@@ -860,6 +923,14 @@ App::App(const char* argv0) {
|
||||
log_write("hello world\n");
|
||||
}
|
||||
|
||||
if (App::GetMtpEnable()) {
|
||||
hazeInitialize(haze_callback);
|
||||
}
|
||||
|
||||
if (App::GetFtpEnable()) {
|
||||
ftpsrv::Init();
|
||||
}
|
||||
|
||||
if (App::GetNxlinkEnable()) {
|
||||
nxlinkInitialize(nxlink_callback);
|
||||
}
|
||||
@@ -1092,6 +1163,16 @@ App::~App() {
|
||||
}
|
||||
}
|
||||
|
||||
if (App::GetMtpEnable()) {
|
||||
log_write("closing mtp\n");
|
||||
hazeExit();
|
||||
}
|
||||
|
||||
if (App::GetFtpEnable()) {
|
||||
log_write("closing ftp\n");
|
||||
ftpsrv::Exit();
|
||||
}
|
||||
|
||||
if (App::GetNxlinkEnable()) {
|
||||
log_write("closing nxlink\n");
|
||||
nxlinkExit();
|
||||
|
||||
@@ -298,7 +298,7 @@ auto DownloadInternal(CURL* curl, DataStruct& chunk, ProgressCallback pcallback,
|
||||
|
||||
fs::CreateDirectoryRecursivelyWithPath(&chunk.fs, tmp_buf);
|
||||
|
||||
if (auto rc = fsFsCreateFile(&chunk.fs, tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (auto rc = fsFsCreateFile(&chunk.fs, tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s\n", tmp_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
|
||||
rc = CreateDirectory(path, ignore_read_only);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder: %s\n", path);
|
||||
return rc;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path,
|
||||
rc = CreateDirectory(path, ignore_read_only);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder recursively: %s\n", path);
|
||||
return rc;
|
||||
}
|
||||
@@ -251,7 +251,7 @@ Result write_entire_file(FsFileSystem* _fs, const FsPath& path, const std::vecto
|
||||
FsNative fs{_fs, false};
|
||||
R_TRY(fs.GetFsOpenResult());
|
||||
|
||||
if (auto rc = fs.CreateFile(path, in.size(), 0, ignore_read_only); R_FAILED(rc) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (auto rc = fs.CreateFile(path, in.size(), 0, ignore_read_only); R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
335
sphaira/source/ftpsrv_helper.cpp
Normal file
335
sphaira/source/ftpsrv_helper.cpp
Normal file
@@ -0,0 +1,335 @@
|
||||
#include "ftpsrv_helper.hpp"
|
||||
#include <ftpsrv.h>
|
||||
#include <ftpsrv_vfs.h>
|
||||
#include "app.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
FtpSrvConfig g_ftpsrv_config = {0};
|
||||
volatile bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
Thread g_thread;
|
||||
std::mutex g_mutex{};
|
||||
FsFileSystem* g_fs;
|
||||
|
||||
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
|
||||
sphaira::App::NotifyFlashLed();
|
||||
}
|
||||
|
||||
void ftp_progress_callback(void) {
|
||||
sphaira::App::NotifyFlashLed();
|
||||
}
|
||||
|
||||
int vfs_fs_set_errno(Result rc) {
|
||||
switch (rc) {
|
||||
case FsError_TargetLocked: errno = EBUSY; break;
|
||||
case FsError_PathNotFound: errno = ENOENT; break;
|
||||
case FsError_PathAlreadyExists: errno = EEXIST; break;
|
||||
case FsError_UsableSpaceNotEnoughMmcCalibration: errno = ENOSPC; break;
|
||||
case FsError_UsableSpaceNotEnoughMmcSafe: errno = ENOSPC; break;
|
||||
case FsError_UsableSpaceNotEnoughMmcUser: errno = ENOSPC; break;
|
||||
case FsError_UsableSpaceNotEnoughMmcSystem: errno = ENOSPC; break;
|
||||
case FsError_UsableSpaceNotEnoughSdCard: errno = ENOSPC; break;
|
||||
case FsError_OutOfRange: errno = ESPIPE; break;
|
||||
case FsError_TooLongPath: errno = ENAMETOOLONG; break;
|
||||
case FsError_UnsupportedWriteForReadOnlyFileSystem: errno = EROFS; break;
|
||||
default: errno = EIO; break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Result flush_buffered_write(struct FtpVfsFile* f) {
|
||||
Result rc;
|
||||
if (R_SUCCEEDED(rc = fsFileSetSize(&f->fd, f->off + f->buf_off))) {
|
||||
rc = fsFileWrite(&f->fd, f->off, f->buf, f->buf_off, FsWriteOption_None);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
void loop(void* arg) {
|
||||
while (!g_should_exit) {
|
||||
ftpsrv_init(&g_ftpsrv_config);
|
||||
while (!g_should_exit) {
|
||||
if (ftpsrv_loop(100) != FTP_API_LOOP_ERROR_OK) {
|
||||
svcSleepThread(1e+6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ftpsrv_exit();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace sphaira::ftpsrv {
|
||||
|
||||
bool Init() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
if (g_is_running) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_fs = fsdevGetDeviceFileSystem("sdmc");
|
||||
|
||||
g_ftpsrv_config.log_callback = ftp_log_callback;
|
||||
g_ftpsrv_config.progress_callback = ftp_progress_callback;
|
||||
g_ftpsrv_config.anon = true;
|
||||
g_ftpsrv_config.timeout = 15;
|
||||
g_ftpsrv_config.port = 5000;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, 0x2C, 2))) {
|
||||
log_write("failed to create nxlink thread: 0x%X\n", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = threadStart(&g_thread))) {
|
||||
log_write("failed to start nxlink thread: 0x%X\n", rc);
|
||||
threadClose(&g_thread);
|
||||
return false;
|
||||
}
|
||||
|
||||
return g_is_running = true;
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
if (g_is_running) {
|
||||
g_is_running = false;
|
||||
}
|
||||
g_should_exit = true;
|
||||
threadWaitForExit(&g_thread);
|
||||
threadClose(&g_thread);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ftpsrv
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define VFS_NX_BUFFER_IO 1
|
||||
|
||||
int ftp_vfs_open(struct FtpVfsFile* f, const char* path, enum FtpVfsOpenMode mode) {
|
||||
u32 open_mode;
|
||||
if (mode == FtpVfsOpenMode_READ) {
|
||||
open_mode = FsOpenMode_Read;
|
||||
f->is_write = false;
|
||||
} else {
|
||||
fsFsCreateFile(g_fs, path, 0, 0);
|
||||
open_mode = FsOpenMode_Write | FsOpenMode_Append;
|
||||
#if !VFS_NX_BUFFER_IO
|
||||
open_mode |= FsOpenMode_Append;
|
||||
#endif
|
||||
f->is_write = true;
|
||||
}
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fsFsOpenFile(g_fs, path, open_mode, &f->fd))) {
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
|
||||
f->off = f->buf_off = f->buf_size = 0;
|
||||
|
||||
if (mode == FtpVfsOpenMode_WRITE) {
|
||||
if (R_FAILED(rc = fsFileSetSize(&f->fd, 0))) {
|
||||
goto fail_close;
|
||||
}
|
||||
} else if (mode == FtpVfsOpenMode_APPEND) {
|
||||
if (R_FAILED(rc = fsFileGetSize(&f->fd, &f->off))) {
|
||||
goto fail_close;
|
||||
}
|
||||
}
|
||||
|
||||
f->is_valid = true;
|
||||
return 0;
|
||||
|
||||
fail_close:
|
||||
fsFileClose(&f->fd);
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
|
||||
int ftp_vfs_read(struct FtpVfsFile* f, void* buf, size_t size) {
|
||||
Result rc;
|
||||
|
||||
#if VFS_NX_BUFFER_IO
|
||||
if (f->buf_off == f->buf_size) {
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(rc = fsFileRead(&f->fd, f->off, f->buf, sizeof(f->buf), FsReadOption_None, &bytes_read))) {
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
|
||||
f->buf_off = 0;
|
||||
f->buf_size = bytes_read;
|
||||
}
|
||||
|
||||
if (!f->buf_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size = size < f->buf_size - f->buf_off ? size : f->buf_size - f->buf_off;
|
||||
memcpy(buf, f->buf + f->buf_off, size);
|
||||
f->off += size;
|
||||
f->buf_off += size;
|
||||
return size;
|
||||
#else
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(rc = fsFileRead(&f->fd, f->off, buf, size, FsReadOption_None, &bytes_read))) {
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
f->off += bytes_read;
|
||||
return bytes_read;
|
||||
#endif
|
||||
}
|
||||
|
||||
int ftp_vfs_write(struct FtpVfsFile* f, const void* buf, size_t size) {
|
||||
Result rc;
|
||||
|
||||
#if VFS_NX_BUFFER_IO
|
||||
const size_t ret = size;
|
||||
while (size) {
|
||||
if (f->buf_off + size > sizeof(f->buf)) {
|
||||
const u64 sz = sizeof(f->buf) - f->buf_off;
|
||||
memcpy(f->buf + f->buf_off, buf, sz);
|
||||
f->buf_off += sz;
|
||||
|
||||
if (R_FAILED(rc = flush_buffered_write(f))) {
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
|
||||
buf += sz;
|
||||
size -= sz;
|
||||
f->off += f->buf_off;
|
||||
f->buf_off = 0;
|
||||
} else {
|
||||
memcpy(f->buf + f->buf_off, buf, size);
|
||||
f->buf_off += size;
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
#else
|
||||
if (R_FAILED(rc = fsFileWrite(&f->fd, f->off, buf, size, FsWriteOption_None))) {
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
f->off += size;
|
||||
return size;
|
||||
const size_t ret = size;
|
||||
#endif
|
||||
}
|
||||
|
||||
// buf and size is the amount of data sent.
|
||||
int ftp_vfs_seek(struct FtpVfsFile* f, const void* buf, size_t size, size_t off) {
|
||||
#if VFS_NX_BUFFER_IO
|
||||
if (!f->is_write) {
|
||||
f->buf_off -= f->off - off;
|
||||
}
|
||||
#endif
|
||||
f->off = off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ftp_vfs_close(struct FtpVfsFile* f) {
|
||||
if (!ftp_vfs_isfile_open(f)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (f->is_write && f->buf_off) {
|
||||
flush_buffered_write(f);
|
||||
}
|
||||
|
||||
fsFileClose(&f->fd);
|
||||
f->is_valid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ftp_vfs_isfile_open(struct FtpVfsFile* f) {
|
||||
return f->is_valid;
|
||||
}
|
||||
|
||||
int ftp_vfs_opendir(struct FtpVfsDir* f, const char* path) {
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fsFsOpenDirectory(g_fs, path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &f->dir))) {
|
||||
return vfs_fs_set_errno(rc);
|
||||
}
|
||||
f->is_valid = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* ftp_vfs_readdir(struct FtpVfsDir* f, struct FtpVfsDirEntry* entry) {
|
||||
Result rc;
|
||||
s64 total_entries;
|
||||
if (R_FAILED(rc = fsDirRead(&f->dir, &total_entries, 1, &entry->buf))) {
|
||||
vfs_fs_set_errno(rc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (total_entries <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return entry->buf.name;
|
||||
}
|
||||
|
||||
int ftp_vfs_dirlstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) {
|
||||
return lstat(path, st);
|
||||
}
|
||||
|
||||
int ftp_vfs_closedir(struct FtpVfsDir* f) {
|
||||
if (!ftp_vfs_isdir_open(f)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fsDirClose(&f->dir);
|
||||
f->is_valid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ftp_vfs_isdir_open(struct FtpVfsDir* f) {
|
||||
return f->is_valid;
|
||||
}
|
||||
|
||||
int ftp_vfs_stat(const char* path, struct stat* st) {
|
||||
return stat(path, st);
|
||||
}
|
||||
|
||||
int ftp_vfs_lstat(const char* path, struct stat* st) {
|
||||
return lstat(path, st);
|
||||
}
|
||||
|
||||
int ftp_vfs_mkdir(const char* path) {
|
||||
return mkdir(path, 0777);
|
||||
}
|
||||
|
||||
int ftp_vfs_unlink(const char* path) {
|
||||
return unlink(path);
|
||||
}
|
||||
|
||||
int ftp_vfs_rmdir(const char* path) {
|
||||
return rmdir(path);
|
||||
}
|
||||
|
||||
int ftp_vfs_rename(const char* src, const char* dst) {
|
||||
return rename(src, dst);
|
||||
}
|
||||
|
||||
int ftp_vfs_readlink(const char* path, char* buf, size_t buflen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* ftp_vfs_getpwuid(const struct stat* st) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* ftp_vfs_getgrgid(const struct stat* st) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -61,6 +61,8 @@ void userAppInit(void) {
|
||||
diagAbortWithResult(rc);
|
||||
if (R_FAILED(rc = setInitialize()))
|
||||
diagAbortWithResult(rc);
|
||||
if (R_FAILED(rc = hidsysInitialize()))
|
||||
diagAbortWithResult(rc);
|
||||
|
||||
log_nxlink_init();
|
||||
}
|
||||
@@ -68,6 +70,7 @@ void userAppInit(void) {
|
||||
void userAppExit(void) {
|
||||
log_nxlink_exit();
|
||||
|
||||
hidsysExit();
|
||||
setExit();
|
||||
accountExit();
|
||||
nifmExit();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -114,7 +115,7 @@ auto recvall(int sock, void* buf, int size) -> bool {
|
||||
if (errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
svcSleepThread(1e+6);
|
||||
} else {
|
||||
got += len;
|
||||
left -= len;
|
||||
@@ -132,7 +133,7 @@ auto sendall(Socket sock, const void* buf, int size) -> bool {
|
||||
if (errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
svcSleepThread(1e+6);
|
||||
}
|
||||
sent += len;
|
||||
left -= len;
|
||||
@@ -171,28 +172,6 @@ auto get_file_data(Socket sock, int max) -> std::vector<u8> {
|
||||
return buf;
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto create_directories(fs::FsNative& fs, const std::string& path) -> Result {
|
||||
std::size_t pos{};
|
||||
|
||||
// no sane person creates 20 directories deep
|
||||
for (int i = 0; i < 20; i++) {
|
||||
pos = path.find_first_of("/", pos);
|
||||
if (pos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
|
||||
fs::FsPath safe_buf;
|
||||
std::strcpy(safe_buf, path.substr(0, pos).c_str());
|
||||
const auto rc = fs.CreateDirectory(safe_buf);
|
||||
R_UNLESS(R_SUCCEEDED(rc) || rc == FsError_ResultPathAlreadyExists, rc);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
void loop(void* args) {
|
||||
log_write("in nxlink thread func\n");
|
||||
const sockaddr_in servaddr{
|
||||
@@ -266,21 +245,36 @@ void loop(void* args) {
|
||||
|
||||
sockaddr_in sa_remote{};
|
||||
|
||||
while (!g_quit) {
|
||||
svcSleepThread(1e+8);
|
||||
pollfd pfds[2];
|
||||
pfds[0].fd = sock;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = sock_udp;
|
||||
pfds[1].events = POLLIN;
|
||||
|
||||
if (poll_network_change()) {
|
||||
while (!g_quit) {
|
||||
auto poll_rc = poll(pfds, std::size(pfds), 1000/60);
|
||||
if (poll_rc < 0) {
|
||||
break;
|
||||
} else if (poll_rc == 0) {
|
||||
continue;
|
||||
} else if ((pfds[0].revents & (POLLERR|POLLHUP|POLLNVAL)) || (pfds[1].revents & (POLLERR|POLLHUP|POLLNVAL))) {
|
||||
break;
|
||||
}
|
||||
|
||||
char recvbuf[256];
|
||||
socklen_t from_len = sizeof(sa_remote);
|
||||
const auto udp_len = recvfrom(sock_udp, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&sa_remote, &from_len);
|
||||
if (udp_len > 0 && !std::strncmp(recvbuf, UDP_MAGIC_SERVER, std::strlen(UDP_MAGIC_SERVER))) {
|
||||
// log_write("got udp len: %d - %.*s\n", udp_len, udp_len, recvbuf);
|
||||
sa_remote.sin_family = AF_INET;
|
||||
sa_remote.sin_port = htons(NXLINK_CLIENT_PORT);
|
||||
sendto(sock_udp, UDP_MAGIC_CLIENT, std::strlen(UDP_MAGIC_CLIENT), 0, (sockaddr*)&sa_remote, sizeof(sa_remote));
|
||||
if (pfds[1].revents & POLLIN) {
|
||||
char recvbuf[6];
|
||||
socklen_t from_len = sizeof(sa_remote);
|
||||
auto udp_len = recvfrom(sock_udp, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&sa_remote, &from_len);
|
||||
if (udp_len == sizeof(recvbuf) && !std::strncmp(recvbuf, UDP_MAGIC_SERVER, std::strlen(UDP_MAGIC_SERVER))) {
|
||||
// log_write("got udp len: %d - %.*s\n", udp_len, udp_len, recvbuf);
|
||||
sa_remote.sin_family = AF_INET;
|
||||
sa_remote.sin_port = htons(NXLINK_CLIENT_PORT);
|
||||
udp_len = sendto(sock_udp, UDP_MAGIC_CLIENT, std::strlen(UDP_MAGIC_CLIENT), 0, (const sockaddr*)&sa_remote, sizeof(sa_remote));
|
||||
if (udp_len != std::strlen(UDP_MAGIC_CLIENT)) {
|
||||
log_write("nxlink failed to send udp packet\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socklen_t accept_len = sizeof(sa_remote);
|
||||
@@ -316,8 +310,9 @@ void loop(void* args) {
|
||||
}
|
||||
|
||||
// check that we have enough space
|
||||
fs::FsNativeSd fs;
|
||||
s64 sd_storage_space_free;
|
||||
if (R_FAILED(fs::FsNativeSd().GetFreeSpace("/", &sd_storage_space_free)) || filesize >= sd_storage_space_free) {
|
||||
if (R_FAILED(fs.GetFreeSpace("/", &sd_storage_space_free)) || filesize >= sd_storage_space_free) {
|
||||
sendall(connfd, &ERR_SPACE, sizeof(ERR_SPACE));
|
||||
continue;
|
||||
}
|
||||
@@ -345,16 +340,6 @@ void loop(void* args) {
|
||||
path = name;
|
||||
}
|
||||
|
||||
// std::strcat(temp_path, "~");
|
||||
|
||||
fs::FsNativeSd fs;
|
||||
|
||||
if (R_FAILED(rc = fs.GetFsOpenResult())) {
|
||||
sendall(connfd, &ERR_FILE, sizeof(ERR_FILE));
|
||||
log_write("failed to open fs: 0x%X\n", socketGetLastResult());
|
||||
continue;
|
||||
}
|
||||
|
||||
// if (R_FAILED(rc = create_directories(fs, path))) {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(path))) {
|
||||
sendall(connfd, &ERR_FILE, sizeof(ERR_FILE));
|
||||
@@ -364,7 +349,7 @@ void loop(void* args) {
|
||||
|
||||
// this is the path we will write to
|
||||
const auto temp_path = path + "~";
|
||||
if (R_FAILED(rc = fs.CreateFile(temp_path, file_data.size(), 0)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc = fs.CreateFile(temp_path, file_data.size(), 0)) && rc != FsError_PathAlreadyExists) {
|
||||
sendall(connfd, &ERR_FILE, sizeof(ERR_FILE));
|
||||
log_write("failed to create file: %X\n", rc);
|
||||
continue;
|
||||
@@ -408,7 +393,7 @@ void loop(void* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fs.DeleteFile(path)) && rc != FsError_ResultPathNotFound) {
|
||||
if (R_FAILED(rc = fs.DeleteFile(path)) && rc != FsError_PathNotFound) {
|
||||
log_write("failed to delete %X\n", rc);
|
||||
continue;
|
||||
}
|
||||
@@ -473,13 +458,14 @@ bool nxlinkInitialize(NxlinkCallback callback) {
|
||||
g_callback = callback;
|
||||
g_quit = false;
|
||||
|
||||
if (R_FAILED(threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, 0x2C, 2))) {
|
||||
log_write("failed to create nxlink thread: 0x%X\n", socketGetLastResult());
|
||||
Result rc;
|
||||
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, 0x2C, 2))) {
|
||||
log_write("failed to create nxlink thread: 0x%X\n", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(threadStart(&g_thread))) {
|
||||
log_write("failed to start nxlink thread: 0x%X\n", socketGetLastResult());
|
||||
if (R_FAILED(rc = threadStart(&g_thread))) {
|
||||
log_write("failed to start nxlink thread: 0x%X\n", rc);
|
||||
threadClose(&g_thread);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
fs.CreateDirectoryRecursivelyWithPath(output, true);
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(output, info.uncompressed_size, 0, true)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc = fs.CreateFile(output, info.uncompressed_size, 0, true)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", output, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <ranges>
|
||||
#include <stack>
|
||||
// #include <stack>
|
||||
#include <expected>
|
||||
|
||||
namespace sphaira::ui::menu::filebrowser {
|
||||
namespace {
|
||||
@@ -61,42 +62,47 @@ constexpr std::string_view IMAGE_EXTENSIONS[] = {
|
||||
"png", "jpg", "jpeg", "bmp", "gif",
|
||||
};
|
||||
|
||||
using PathPair = std::pair<std::string_view, std::string_view>;
|
||||
constexpr PathPair PATHS[]{
|
||||
PathPair{"3do", "The 3DO Company - 3DO"},
|
||||
PathPair{"atari800", "Atari - 8-bit"},
|
||||
PathPair{"atari2600", "Atari - 2600"},
|
||||
PathPair{"atari5200", "Atari - 5200"},
|
||||
PathPair{"atari7800", "Atari - 7800"},
|
||||
PathPair{"atarilynx", "Atari - Lynx"},
|
||||
PathPair{"atarijaguar", "Atari - Jaguar"},
|
||||
PathPair{"atarijaguarcd", ""},
|
||||
PathPair{"n3ds", "Nintendo - Nintendo 3DS"},
|
||||
PathPair{"n64", "Nintendo - Nintendo 64"},
|
||||
PathPair{"nds", "Nintendo - Nintendo DS"},
|
||||
PathPair{"fds", "Nintendo - Famicom Disk System"},
|
||||
PathPair{"nes", "Nintendo - Nintendo Entertainment System"},
|
||||
PathPair{"pokemini", "Nintendo - Pokemon Mini"},
|
||||
PathPair{"gb", "Nintendo - Game Boy"},
|
||||
PathPair{"gba", "Nintendo - Game Boy Advance"},
|
||||
PathPair{"gbc", "Nintendo - Game Boy Color"},
|
||||
PathPair{"virtualboy", "Nintendo - Virtual Boy"},
|
||||
PathPair{"gameandwatch", ""},
|
||||
PathPair{"sega32x", "Sega - 32X"},
|
||||
PathPair{"segacd", "Sega - Mega CD - Sega CD"},
|
||||
PathPair{"dreamcast", "Sega - Dreamcast"},
|
||||
PathPair{"gamegear", "Sega - Game Gear"},
|
||||
PathPair{"genesis", "Sega - Mega Drive - Genesis"},
|
||||
PathPair{"mastersystem", "Sega - Master System - Mark III"},
|
||||
PathPair{"megadrive", "Sega - Mega Drive - Genesis"},
|
||||
PathPair{"saturn", "Sega - Saturn"},
|
||||
PathPair{"sg-1000", "Sega - SG-1000"},
|
||||
PathPair{"psx", "Sony - PlayStation"},
|
||||
PathPair{"psp", "Sony - PlayStation Portable"},
|
||||
PathPair{"snes", "Nintendo - Super Nintendo Entertainment System"},
|
||||
PathPair{"pico8", "Sega - PICO"},
|
||||
PathPair{"wonderswan", "Bandai - WonderSwan"},
|
||||
PathPair{"wonderswancolor", "Bandai - WonderSwan Color"},
|
||||
struct RomDatabaseEntry {
|
||||
std::string_view folder;
|
||||
std::string_view database;
|
||||
};
|
||||
|
||||
// using PathPair = std::pair<std::string_view, std::string_view>;
|
||||
constexpr RomDatabaseEntry PATHS[]{
|
||||
{ "3do", "The 3DO Company - 3DO"},
|
||||
{ "atari800", "Atari - 8-bit"},
|
||||
{ "atari2600", "Atari - 2600"},
|
||||
{ "atari5200", "Atari - 5200"},
|
||||
{ "atari7800", "Atari - 7800"},
|
||||
{ "atarilynx", "Atari - Lynx"},
|
||||
{ "atarijaguar", "Atari - Jaguar"},
|
||||
{ "atarijaguarcd", ""},
|
||||
{ "n3ds", "Nintendo - Nintendo 3DS"},
|
||||
{ "n64", "Nintendo - Nintendo 64"},
|
||||
{ "nds", "Nintendo - Nintendo DS"},
|
||||
{ "fds", "Nintendo - Famicom Disk System"},
|
||||
{ "nes", "Nintendo - Nintendo Entertainment System"},
|
||||
{ "pokemini", "Nintendo - Pokemon Mini"},
|
||||
{ "gb", "Nintendo - Game Boy"},
|
||||
{ "gba", "Nintendo - Game Boy Advance"},
|
||||
{ "gbc", "Nintendo - Game Boy Color"},
|
||||
{ "virtualboy", "Nintendo - Virtual Boy"},
|
||||
{ "gameandwatch", ""},
|
||||
{ "sega32x", "Sega - 32X"},
|
||||
{ "segacd", "Sega - Mega CD - Sega CD"},
|
||||
{ "dreamcast", "Sega - Dreamcast"},
|
||||
{ "gamegear", "Sega - Game Gear"},
|
||||
{ "genesis", "Sega - Mega Drive - Genesis"},
|
||||
{ "mastersystem", "Sega - Master System - Mark III"},
|
||||
{ "megadrive", "Sega - Mega Drive - Genesis"},
|
||||
{ "saturn", "Sega - Saturn"},
|
||||
{ "sg-1000", "Sega - SG-1000"},
|
||||
{ "psx", "Sony - PlayStation"},
|
||||
{ "psp", "Sony - PlayStation Portable"},
|
||||
{ "snes", "Nintendo - Super Nintendo Entertainment System"},
|
||||
{ "pico8", "Sega - PICO"},
|
||||
{ "wonderswan", "Bandai - WonderSwan"},
|
||||
{ "wonderswancolor", "Bandai - WonderSwan Color"},
|
||||
};
|
||||
|
||||
constexpr fs::FsPath DAYBREAK_PATH{"/switch/daybreak.nro"};
|
||||
@@ -127,47 +133,49 @@ auto IsExtension(std::string_view ext1, std::string_view ext2) -> bool {
|
||||
// tries to find database path using folder name
|
||||
// names are taken from retropie
|
||||
// retroarch database names can also be used
|
||||
auto GetRomDatabaseFromPath(const std::string& path) -> std::string {
|
||||
auto GetRomDatabaseFromPath(std::string_view path) -> int {
|
||||
if (path.length() <= 1) {
|
||||
return {};
|
||||
return -1;
|
||||
}
|
||||
|
||||
// this won't fail :)
|
||||
const auto db_name = path.substr(path.find_last_of('/') + 1);
|
||||
log_write("new path: %s\n", db_name.c_str());
|
||||
// log_write("new path: %s\n", db_name.c_str());
|
||||
|
||||
for (auto& [folder_name, database_name] : PATHS) {
|
||||
for (int i = 0; i < std::size(PATHS); i++) {
|
||||
auto& p = PATHS[i];
|
||||
if ((
|
||||
folder_name.length() == db_name.length() && !strncasecmp(folder_name.data(), db_name.data(), folder_name.length())) ||
|
||||
(database_name.length() == db_name.length() && !strncasecmp(database_name.data(), db_name.data(), database_name.length()))) {
|
||||
log_write("found it :) %s\n", std::string{database_name.data(), database_name.length()}.c_str());
|
||||
return std::string{database_name.data(), database_name.length()};
|
||||
p.folder.length() == db_name.length() && !strncasecmp(p.folder.data(), db_name.data(), p.folder.length())) ||
|
||||
(p.database.length() == db_name.length() && !strncasecmp(p.database.data(), db_name.data(), p.database.length()))) {
|
||||
log_write("found it :) %.*s\n", p.database.length(), p.database.data());
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// if we failed, try again but with the folder about
|
||||
// "/roms/psx/scooby-doo/scooby-doo.bin", this will check psx
|
||||
const auto last_off = path.substr(0, path.find_last_of('/'));
|
||||
if (const auto off = last_off.find_last_of('/'); off != std::string::npos) {
|
||||
if (const auto off = last_off.find_last_of('/'); off != std::string_view::npos) {
|
||||
const auto db_name2 = last_off.substr(off + 1);
|
||||
printf("got db: %s\n", db_name2.c_str());
|
||||
for (auto& [folder_name, database_name] : PATHS) {
|
||||
// printf("got db: %s\n", db_name2.c_str());
|
||||
for (int i = 0; i < std::size(PATHS); i++) {
|
||||
auto& p = PATHS[i];
|
||||
if ((
|
||||
folder_name.length() == db_name2.length() && !strcasecmp(folder_name.data(), db_name2.data())) ||
|
||||
(database_name.length() == db_name2.length() && !strcasecmp(database_name.data(), db_name2.data()))) {
|
||||
log_write("found it :) %s\n", std::string{database_name.data(), database_name.length()}.c_str());
|
||||
return std::string{database_name.data(), database_name.length()};
|
||||
p.folder.length() == db_name2.length() && !strcasecmp(p.folder.data(), db_name2.data())) ||
|
||||
(p.database.length() == db_name2.length() && !strcasecmp(p.database.data(), db_name2.data()))) {
|
||||
log_write("found it :) %.*s\n", p.database.length(), p.database.data());
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
return -1;
|
||||
}
|
||||
|
||||
//
|
||||
auto GetRomIcon(ProgressBox* pbox, std::string filename, std::string extension, std::string database, const NroEntry& nro) {
|
||||
auto GetRomIcon(ProgressBox* pbox, std::string filename, std::string extension, int db_idx, const NroEntry& nro) {
|
||||
// if no db entries, use nro icon
|
||||
if (database.empty()) {
|
||||
if (db_idx < 0) {
|
||||
log_write("using nro image\n");
|
||||
return nro_get_icon(nro.path, nro.icon_size, nro.icon_offset);
|
||||
}
|
||||
@@ -189,7 +197,7 @@ auto GetRomIcon(ProgressBox* pbox, std::string filename, std::string extension,
|
||||
#define RA_THUMBNAIL_PATH "/retroarch/thumbnails/"
|
||||
#define RA_BOXART_EXT ".png"
|
||||
|
||||
const std::string system_name = database;//GetDatabaseFromExt(database, extension);
|
||||
const auto system_name = std::string{PATHS[db_idx].database.data(), PATHS[db_idx].database.length()};//GetDatabaseFromExt(database, extension);
|
||||
auto system_name_gh = system_name + "/master";
|
||||
for (auto& c : system_name_gh) {
|
||||
if (c == ' ') {
|
||||
@@ -601,7 +609,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
}, true));
|
||||
|
||||
if (m_entries_current.size()) {
|
||||
if (m_entries_current.size() && !m_selected_count && GetEntry().IsFile() && GetEntry().file_size < 1024*64) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("View as text (unfinished)"_i18n, [this](){
|
||||
App::Push(std::make_shared<fileview::Menu>(GetNewPathCurrent()));
|
||||
}, true));
|
||||
@@ -864,7 +872,7 @@ void Menu::InstallForwarder() {
|
||||
log_write("got filename2: %s\n\n", file_name.c_str());
|
||||
}
|
||||
|
||||
const auto database = GetRomDatabaseFromPath(m_path);
|
||||
const auto db_idx = GetRomDatabaseFromPath(m_path);
|
||||
|
||||
OwoConfig config{};
|
||||
config.nro_path = assoc.path.toString();
|
||||
@@ -872,7 +880,7 @@ void Menu::InstallForwarder() {
|
||||
config.name = nro.nacp.lang[0].name + std::string{" | "} + file_name;
|
||||
// config.name = file_name;
|
||||
config.nacp = nro.nacp;
|
||||
config.icon = GetRomIcon(pbox, file_name, extension, database, nro);
|
||||
config.icon = GetRomIcon(pbox, file_name, extension, db_idx, nro);
|
||||
|
||||
return R_SUCCEEDED(App::Install(pbox, config));
|
||||
}));
|
||||
@@ -966,24 +974,24 @@ auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
|
||||
|
||||
auto Menu::FindFileAssocFor() -> std::vector<FileAssocEntry> {
|
||||
// only support roms in correctly named folders, sorry!
|
||||
const auto database_name = GetRomDatabaseFromPath(m_path);
|
||||
const auto db_idx = GetRomDatabaseFromPath(m_path);
|
||||
const auto& entry = GetEntry();
|
||||
const auto extension = entry.internal_extension.empty() ? entry.extension : entry.internal_extension;
|
||||
if (extension.empty()) {
|
||||
log_write("failed to get extension for db: %s path: %s\n", database_name.c_str(), m_path);
|
||||
// log_write("failed to get extension for db: %s path: %s\n", database_entry.c_str(), m_path);
|
||||
return {};
|
||||
}
|
||||
|
||||
log_write("got extension for db: %s path: %s\n", database_name.c_str(), m_path);
|
||||
// log_write("got extension for db: %s path: %s\n", database_entry.c_str(), m_path);
|
||||
|
||||
|
||||
std::vector<FileAssocEntry> out_entries;
|
||||
if (!database_name.empty()) {
|
||||
if (db_idx >= 0) {
|
||||
// if database isn't empty, then we are in a valid folder
|
||||
// search for an entry that matches the db and ext
|
||||
for (const auto& assoc : m_assoc_entries) {
|
||||
for (const auto& assoc_db : assoc.database) {
|
||||
if (assoc_db == database_name) {
|
||||
if (assoc_db == PATHS[db_idx].folder || assoc_db == PATHS[db_idx].database) {
|
||||
for (const auto& assoc_ext : assoc.ext) {
|
||||
if (assoc_ext == extension) {
|
||||
log_write("found ext: %s assoc_ext: %s assoc.ext: %s\n", assoc.path, assoc_ext.c_str(), extension.c_str());
|
||||
@@ -1021,76 +1029,60 @@ void Menu::LoadAssocEntriesPath(const fs::FsPath& path) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT(closedir(dir));
|
||||
fs::FsNativeSd fs;
|
||||
|
||||
while (auto d = readdir(dir)) {
|
||||
if (d->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// const std::string name = d->d_name;
|
||||
const auto ext = std::strrchr(d->d_name, '.');
|
||||
if (!ext) {
|
||||
continue;
|
||||
}
|
||||
if (strcasecmp(ext, ".ini")) {
|
||||
if (!ext || strcasecmp(ext, ".ini")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto full_path = GetNewPath(path, d->d_name);
|
||||
|
||||
fs::FsPath buf{};
|
||||
const auto ext_len = 1 + ini_gets("config", "supported_extensions", "", buf, sizeof(buf) - 1, full_path);
|
||||
if (ext_len <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// log_write("reading ini: %s\n", name.c_str());
|
||||
|
||||
FileAssocEntry assoc{};
|
||||
|
||||
for (int i = 0; i < ext_len; i++) {
|
||||
for (int j = i; j < ext_len; j++) {
|
||||
if (buf[j] == '|' || buf[j] == '\0') {
|
||||
assoc.ext.emplace_back(buf + i, j - i);
|
||||
i += j - i;
|
||||
break;
|
||||
ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) {
|
||||
auto assoc = static_cast<FileAssocEntry*>(UserData);
|
||||
if (!strcmp(Key, "path")) {
|
||||
assoc->path = Value;
|
||||
} else if (!strcmp(Key, "supported_extensions")) {
|
||||
for (int i = 0; Value[i]; i++) {
|
||||
for (int j = i; ; j++) {
|
||||
if (Value[j] == '|' || Value[j] == '\0') {
|
||||
assoc->ext.emplace_back(Value + i, j - i);
|
||||
i += j - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(Key, "database")) {
|
||||
for (int i = 0; Value[i]; i++) {
|
||||
for (int j = i; ; j++) {
|
||||
if (Value[j] == '|' || Value[j] == '\0') {
|
||||
assoc->database.emplace_back(Value + i, j - i);
|
||||
i += j - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}, &assoc, full_path);
|
||||
|
||||
if (assoc.ext.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// assoc.name = name.substr(0, name.find_last_of('.'));
|
||||
assoc.name.assign(d->d_name, ext - d->d_name);
|
||||
|
||||
const auto path_len = ini_gets("config", "path", "", buf, sizeof(buf) - 1, full_path);
|
||||
if (path_len > 0) {
|
||||
assoc.path = buf;
|
||||
}
|
||||
|
||||
const auto database_len = 1 + ini_gets("config", "database", "", buf, sizeof(buf) - 1, full_path);
|
||||
if (database_len > 1) {
|
||||
for (int i = 0; i < database_len; i++) {
|
||||
for (int j = i; j < database_len; j++) {
|
||||
if (buf[j] == '|' || buf[j] == '\0') {
|
||||
assoc.database.emplace_back(buf + i, j - i);
|
||||
i += j - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool file_exists{};
|
||||
|
||||
// if path isn't empty, check if the file exists
|
||||
bool file_exists{};
|
||||
if (!assoc.path.empty()) {
|
||||
file_exists = fs::FsNativeSd().FileExists(assoc.path);
|
||||
file_exists = fs.FileExists(assoc.path);
|
||||
} else {
|
||||
log_write("\tpath is empty\n");
|
||||
#if 1
|
||||
const auto nro_name = assoc.name + ".nro";
|
||||
for (const auto& nro : m_nro_entries) {
|
||||
const auto len = std::strlen(nro.path);
|
||||
@@ -1103,7 +1095,6 @@ void Menu::LoadAssocEntriesPath(const fs::FsPath& path) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// after all of that, the file doesn't exist :(
|
||||
@@ -1135,32 +1126,6 @@ void Menu::LoadAssocEntries() {
|
||||
LoadAssocEntriesPath("/config/sphaira/assoc/");
|
||||
}
|
||||
|
||||
#if 0
|
||||
void Menu::OnIndexChange() {
|
||||
if (!GetEntry().checked_internal_extension && GetEntry().extension == "zip") {
|
||||
GetEntry().checked_internal_extension = true;
|
||||
if (auto zfile = unzOpen64(GetNewPathCurrent())) {
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
unz_global_info gi{};
|
||||
// only check first entry (i think RA does the same)
|
||||
if (UNZ_OK == unzGetGlobalInfo(zfile, &gi) && gi.number_entry >= 1) {
|
||||
fs::FsPath filename_inzip{};
|
||||
unz_file_info64 file_info{};
|
||||
if (UNZ_OK == unzOpenCurrentFile(zfile)) {
|
||||
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
|
||||
if (UNZ_OK == unzGetCurrentFileInfo64(zfile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0)) {
|
||||
if (auto ext = std::strrchr(filename_inzip, '.')) {
|
||||
GetEntry().internal_name = filename_inzip.toString();
|
||||
GetEntry().internal_extension = ext+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Menu::Sort() {
|
||||
// returns true if lhs should be before rhs
|
||||
const auto sort = m_sort.Get();
|
||||
|
||||
@@ -91,13 +91,13 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
|
||||
|
||||
Result rc;
|
||||
if (file_path[strlen(file_path) -1] == '/') {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
@@ -249,6 +249,14 @@ MainMenu::MainMenu() {
|
||||
auto options = std::make_shared<Sidebar>("Network Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ftp"_i18n, App::GetFtpEnable(), [this](bool& enable){
|
||||
App::SetFtpEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Mtp"_i18n, App::GetMtpEnable(), [this](bool& enable){
|
||||
App::SetMtpEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [this](bool& enable){
|
||||
App::SetNxlinkEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
@@ -369,7 +369,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
|
||||
const auto file_path = fs::AppendPath(dir_path, name);
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ auto NotifMananger::Clear(NotifEntry::Side side) -> void {
|
||||
}
|
||||
|
||||
auto NotifMananger::Clear() -> void {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
m_entries_left.clear();
|
||||
m_entries_right.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user