add mtp (haze) ftp (ftpsrv), update RA file assoc, nxlink now polls for connection.

This commit is contained in:
ITotalJustice
2024-12-25 22:17:21 +00:00
parent a2c9b63dfd
commit 37890f157d
52 changed files with 742 additions and 289 deletions

View File

@@ -1,4 +0,0 @@
[config]
path=/retroarch/cores/2048_libretro_libnx.nro
supported_extensions=
database=2048

View 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

View File

@@ -0,0 +1,4 @@
[config]
path=/retroarch/cores/ardens_libretro_libnx.nro
supported_extensions=hex|arduboy
database=Arduboy Inc - Arduboy

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/arduous_libretro_libnx.nro path=/retroarch/cores/arduous_libretro_libnx.nro
supported_extensions=hex supported_extensions=hex
database=Arduboy database=Arduboy Inc - Arduboy

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/atari800_libretro_libnx.nro 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 database=Atari - 5200|Atari - 8-bit

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/bluemsx_libretro_libnx.nro path=/retroarch/cores/bluemsx_libretro_libnx.nro
supported_extensions=rom|ri|mx1|mx2|col|dsk|cas|sg|sc|sf|m3u supported_extensions=rom|ri|mx1|mx2|dsk|col|sg|sc|sf|cas|m3u
database=Microsoft - MSX|Microsoft - MSX2|Coleco - ColecoVision|Sega - SG-1000 database=Microsoft - MSX|Microsoft - MSX2|Coleco - ColecoVision|Sega - SG-1000|Spectravideo - SVI-318 - SVI-328

View File

@@ -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

View 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

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/dosbox_svn_libretro_libnx.nro 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 database=DOS

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/fbalpha2012_cps1_libretro_libnx.nro
supported_extensions=zip

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/fbalpha2012_cps2_libretro_libnx.nro
supported_extensions=zip

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/fbalpha2012_libretro_libnx.nro
supported_extensions=iso|zip|7z

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/fbalpha2012_neogeo_libretro_libnx.nro
supported_extensions=zip

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/frodo_libretro_libnx.nro 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 database=Commodore - 64

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/fuse_libretro_libnx.nro 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 database=Sinclair - ZX Spectrum +3|Sinclair - ZX Spectrum

View File

@@ -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

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/gong_libretro_libnx.nro
supported_extensions=

View File

@@ -0,0 +1,4 @@
[config]
path=/retroarch/cores/gpsp_libretro_libnx.nro
supported_extensions=gba|bin
database=Nintendo - Game Boy Advance

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/handy_libretro_libnx.nro path=/retroarch/cores/handy_libretro_libnx.nro
supported_extensions=lnx|o supported_extensions=lnx|lyx|o
database=Atari - Lynx database=Atari - Lynx

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/mame2000_libretro_libnx.nro path=/retroarch/cores/mame2000_libretro_libnx.nro
supported_extensions=zip|7z|chd supported_extensions=zip|7z
database=MAME 2000 database=MAME 2000

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/mednafen_lynx_libretro_libnx.nro path=/retroarch/cores/mednafen_lynx_libretro_libnx.nro
supported_extensions=lnx|o supported_extensions=lnx|lyx|o
database=Atari - Lynx database=Atari - Lynx

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/minivmac_libretro_libnx.nro
supported_extensions=dsk|img|zip|hvf|cmd

View File

@@ -0,0 +1,4 @@
[config]
path=/retroarch/cores/mrboom_libretro_libnx.nro
supported_extensions=desktop
database=MrBoom

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/mu_libretro_libnx.nro
supported_extensions=prc|pqa|img|pdb|zip

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/numero_libretro_libnx.nro
supported_extensions=8xp|8xk|8xg

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/pcsx_rearmed_libretro_libnx.nro 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 database=Sony - PlayStation

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/pocketcdg_libretro_libnx.nro
supported_extensions=cdg

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/ppsspp_libretro_libnx.nro 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 database=Sony - PlayStation Portable

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/px68k_libretro_libnx.nro 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 database=Sharp - X68000

View File

@@ -1,3 +1,4 @@
[config] [config]
path=/retroarch/cores/quasi88_libretro_libnx.nro path=/retroarch/cores/quasi88_libretro_libnx.nro
supported_extensions=d88|u88|m3u supported_extensions=d88|u88|m3u
database=NEC - PC-8001 - PC-8801

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/retro8_libretro_libnx.nro path=/retroarch/cores/retro8_libretro_libnx.nro
supported_extensions=p8|png supported_extensions=p8|png
database=PICO8 database=PICO-8

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/stella_libretro_libnx.nro path=/retroarch/cores/stella2023_libretro_libnx.nro
supported_extensions=a26|bin supported_extensions=a26|bin
database=Atari - 2600 database=Atari - 2600

View File

@@ -1,3 +0,0 @@
[config]
path=/retroarch/cores/superbroswar_libretro_libnx.nro
supported_extensions=game

View File

@@ -0,0 +1,4 @@
[config]
path=/retroarch/cores/vircon32_libretro_libnx.nro
supported_extensions=v32|V32
database=Vircon32

View File

@@ -1,4 +1,4 @@
[config] [config]
path=/retroarch/cores/x1_libretro_libnx.nro path=/retroarch/cores/x1_libretro_libnx.nro
supported_extensions=dx1|zip|2d|2hd|tfd|d88|88d|hdm|xdf|dup|tap|cmd supported_extensions=dx1|zip|2d|2hd|tfd|d88|88d|hdm|xdf|dup|tap|cmd
database=Sharp X1 database=Sharp - X1

View File

@@ -72,6 +72,7 @@ add_executable(sphaira
source/swkbd.cpp source/swkbd.cpp
source/web.cpp source/web.cpp
source/i18n.cpp source/i18n.cpp
source/ftpsrv_helper.cpp
) )
target_compile_definitions(sphaira PRIVATE target_compile_definitions(sphaira PRIVATE
@@ -82,6 +83,16 @@ target_compile_definitions(sphaira PRIVATE
include(FetchContent) include(FetchContent)
set(FETCHCONTENT_QUIET FALSE) 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 FetchContent_Declare(libpulsar
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
GIT_TAG d729be3 GIT_TAG d729be3
@@ -135,7 +146,16 @@ set(YYJSON_DISABLE_NON_STANDARD ON)
set(YYJSON_DISABLE_UTF8_VALIDATION ON) set(YYJSON_DISABLE_UTF8_VALIDATION ON)
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF) 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( FetchContent_MakeAvailable(
ftpsrv
libhaze
libpulsar libpulsar
nanovg nanovg
stb stb
@@ -143,6 +163,31 @@ FetchContent_MakeAvailable(
yyjson 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 # todo: upstream cmake
add_library(libpulsar add_library(libpulsar
${libpulsar_SOURCE_DIR}/src/archive/archive_file.c ${libpulsar_SOURCE_DIR}/src/archive/archive_file.c
@@ -195,6 +240,8 @@ set_target_properties(sphaira PROPERTIES
) )
target_link_libraries(sphaira PRIVATE target_link_libraries(sphaira PRIVATE
ftpsrv
libhaze
libpulsar libpulsar
minIni-sphaira minIni-sphaira
nanovg nanovg

View File

@@ -46,11 +46,12 @@ public:
static auto GetVg() -> NVGcontext*; static auto GetVg() -> NVGcontext*;
static void Push(std::shared_ptr<ui::Widget>); 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(std::string text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
static void Notify(ui::NotifEntry entry); static void Notify(ui::NotifEntry entry);
static void NotifyPop(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT); static void NotifyPop(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
static void NotifyClear(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 auto GetThemeMetaList() -> std::span<ThemeMeta>;
static void SetTheme(u64 theme_index); static void SetTheme(u64 theme_index);
@@ -61,6 +62,8 @@ public:
// returns true if we are hbmenu. // returns true if we are hbmenu.
static auto IsHbmenu() -> bool; static auto IsHbmenu() -> bool;
static auto GetMtpEnable() -> bool;
static auto GetFtpEnable() -> bool;
static auto GetNxlinkEnable() -> bool; static auto GetNxlinkEnable() -> bool;
static auto GetLogEnable() -> bool; static auto GetLogEnable() -> bool;
static auto GetReplaceHbmenuEnable() -> bool; static auto GetReplaceHbmenuEnable() -> bool;
@@ -71,6 +74,8 @@ public:
static auto GetThemeMusicEnable() -> bool; static auto GetThemeMusicEnable() -> bool;
static auto GetLanguage() -> long; static auto GetLanguage() -> long;
static void SetMtpEnable(bool enable);
static void SetFtpEnable(bool enable);
static void SetNxlinkEnable(bool enable); static void SetNxlinkEnable(bool enable);
static void SetLogEnable(bool enable); static void SetLogEnable(bool enable);
static void SetReplaceHbmenuEnable(bool enable); static void SetReplaceHbmenuEnable(bool enable);
@@ -138,6 +143,8 @@ public:
bool m_quit{}; bool m_quit{};
option::OptionBool m_nxlink_enabled{INI_SECTION, "nxlink_enabled", true}; 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_log_enabled{INI_SECTION, "log_enabled", false};
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false}; option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
option::OptionBool m_install{INI_SECTION, "install", false}; option::OptionBool m_install{INI_SECTION, "install", false};

View File

@@ -224,18 +224,18 @@ enum SvcError {
}; };
enum FsError { enum FsError {
FsError_ResultPathNotFound = 0x202, FsError_PathNotFound = 0x202,
FsError_ResultPathAlreadyExists = 0x402, FsError_PathAlreadyExists = 0x402,
FsError_ResultTargetLocked = 0xE02, FsError_TargetLocked = 0xE02,
FsError_UsableSpaceNotEnoughMmcCalibration = 0x4602, FsError_UsableSpaceNotEnoughMmcCalibration = 0x4602,
FsError_UsableSpaceNotEnoughMmcSafe = 0x4802, FsError_UsableSpaceNotEnoughMmcSafe = 0x4802,
FsError_UsableSpaceNotEnoughMmcUser = 0x4A02, FsError_UsableSpaceNotEnoughMmcUser = 0x4A02,
FsError_UsableSpaceNotEnoughMmcSystem = 0x4C02, FsError_UsableSpaceNotEnoughMmcSystem = 0x4C02,
FsError_ResultUsableSpaceNotEnoughSdCard = 0x4E02, FsError_UsableSpaceNotEnoughSdCard = 0x4E02,
FsError_ResultUnsupportedSdkVersion = 0x6402, FsError_UnsupportedSdkVersion = 0x6402,
FsError_ResultMountNameAlreadyExists = 0x7802, FsError_MountNameAlreadyExists = 0x7802,
FsError_ResultPartitionNotFound = 0x7D202, FsError_PartitionNotFound = 0x7D202,
FsError_ResultTargetNotFound = 0x7D402, FsError_TargetNotFound = 0x7D402,
FsError_PortSdCardNoDevice = 0xFA202, FsError_PortSdCardNoDevice = 0xFA202,
FsError_GameCardCardNotInserted = 0x13B002, FsError_GameCardCardNotInserted = 0x13B002,
FsError_GameCardCardNotActivated = 0x13B402, FsError_GameCardCardNotActivated = 0x13B402,
@@ -286,9 +286,9 @@ enum FsError {
FsError_GameCardFsCheckHandleInGetStatusFailure = 0x171402, FsError_GameCardFsCheckHandleInGetStatusFailure = 0x171402,
FsError_GameCardFsCheckHandleInCreateReadOnlyFailure = 0x172002, FsError_GameCardFsCheckHandleInCreateReadOnlyFailure = 0x172002,
FsError_GameCardFsCheckHandleInCreateSecureReadOnlyFailure = 0x172202, FsError_GameCardFsCheckHandleInCreateSecureReadOnlyFailure = 0x172202,
FsError_ResultNotImplemented = 0x177202, FsError_NotImplemented = 0x177202,
FsError_ResultAlreadyExists = 0x177602, FsError_AlreadyExists = 0x177602,
FsError_ResultOutOfRange = 0x177A02, FsError_OutOfRange = 0x177A02,
FsError_AllocationMemoryFailedInFatFileSystemA = 0x190202, FsError_AllocationMemoryFailedInFatFileSystemA = 0x190202,
FsError_AllocationMemoryFailedInFatFileSystemB = 0x190402, FsError_AllocationMemoryFailedInFatFileSystemB = 0x190402,
FsError_AllocationMemoryFailedInFatFileSystemC = 0x190602, FsError_AllocationMemoryFailedInFatFileSystemC = 0x190602,
@@ -348,18 +348,18 @@ enum FsError {
FsError_FatFsFormatIllegalSectorsC = 0x280C02, FsError_FatFsFormatIllegalSectorsC = 0x280C02,
FsError_FatFsFormatIllegalSectorsD = 0x280E02, FsError_FatFsFormatIllegalSectorsD = 0x280E02,
FsError_UnexpectedInMountTableA = 0x296A02, FsError_UnexpectedInMountTableA = 0x296A02,
FsError_ResultTooLongPath = 0x2EE602, FsError_TooLongPath = 0x2EE602,
FsError_ResultInvalidCharacter = 0x2EE802, FsError_InvalidCharacter = 0x2EE802,
FsError_ResultInvalidPathFormat = 0x2EEA02, FsError_InvalidPathFormat = 0x2EEA02,
FsError_ResultDirectoryUnobtainable = 0x2EEC02, FsError_DirectoryUnobtainable = 0x2EEC02,
FsError_ResultInvalidOffset = 0x2F5A02, FsError_InvalidOffset = 0x2F5A02,
FsError_ResultInvalidSize = 0x2F5C02, FsError_InvalidSize = 0x2F5C02,
FsError_ResultNullptrArgument = 0x2F5E02, FsError_NullptrArgument = 0x2F5E02,
FsError_ResultInvalidAlignment = 0x2F6002, FsError_InvalidAlignment = 0x2F6002,
FsError_ResultInvalidMountName = 0x2F6202, FsError_InvalidMountName = 0x2F6202,
FsError_ResultExtensionSizeTooLarge = 0x2F6402, FsError_ExtensionSizeTooLarge = 0x2F6402,
FsError_ResultExtensionSizeInvalid = 0x2F6602, FsError_ExtensionSizeInvalid = 0x2F6602,
FsError_ResultFileExtensionWithoutOpenModeAllowAppend = 0x307202, FsError_FileExtensionWithoutOpenModeAllowAppend = 0x307202,
FsError_UnsupportedCommitTarget = 0x313A02, FsError_UnsupportedCommitTarget = 0x313A02,
FsError_UnsupportedSetSizeForNotResizableSubStorage = 0x313C02, FsError_UnsupportedSetSizeForNotResizableSubStorage = 0x313C02,
FsError_UnsupportedSetSizeForResizableSubStorage = 0x313E02, FsError_UnsupportedSetSizeForResizableSubStorage = 0x313E02,
@@ -444,14 +444,14 @@ enum FsError {
FsError_UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem = 0x31E002, FsError_UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem = 0x31E002,
FsError_UnsupportedWriteForZeroBitmapHashStorageFile = 0x31E202, FsError_UnsupportedWriteForZeroBitmapHashStorageFile = 0x31E202,
FsError_UnsupportedSetSizeForZeroBitmapHashStorageFile = 0x31E402, FsError_UnsupportedSetSizeForZeroBitmapHashStorageFile = 0x31E402,
FsError_ResultNcaExternalKeyUnregisteredDeprecated = 0x326602, FsError_NcaExternalKeyUnregisteredDeprecated = 0x326602,
FsError_ResultFileNotClosed = 0x326E02, FsError_FileNotClosed = 0x326E02,
FsError_ResultDirectoryNotClosed = 0x327002, FsError_DirectoryNotClosed = 0x327002,
FsError_ResultWriteModeFileNotClosed = 0x327202, FsError_WriteModeFileNotClosed = 0x327202,
FsError_ResultAllocatorAlreadyRegistered = 0x327402, FsError_AllocatorAlreadyRegistered = 0x327402,
FsError_ResultDefaultAllocatorAlreadyUsed = 0x327602, FsError_DefaultAllocatorAlreadyUsed = 0x327602,
FsError_ResultAllocatorAlignmentViolation = 0x327A02, FsError_AllocatorAlignmentViolation = 0x327A02,
FsError_ResultUserNotExist = 0x328202, FsError_UserNotExist = 0x328202,
FsError_FileNotFound = 0x339402, FsError_FileNotFound = 0x339402,
FsError_DirectoryNotFound = 0x339602, FsError_DirectoryNotFound = 0x339602,
FsError_MappingTableFull = 0x346402, FsError_MappingTableFull = 0x346402,

View File

@@ -7,6 +7,7 @@
#include <string> #include <string>
#include <switch.h> #include <switch.h>
#include <nxlink.h> #include <nxlink.h>
#include <haze.h>
#include "download.hpp" #include "download.hpp"
namespace sphaira::evman { namespace sphaira::evman {
@@ -23,6 +24,7 @@ struct ExitEventData {
using EventData = std::variant< using EventData = std::variant<
LaunchNroEventData, LaunchNroEventData,
ExitEventData, ExitEventData,
HazeCallbackData,
NxlinkCallbackData, NxlinkCallbackData,
DownloadEventData DownloadEventData
>; >;

View 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

View File

@@ -110,19 +110,18 @@ private:
void LoadAssocEntriesPath(const fs::FsPath& path); void LoadAssocEntriesPath(const fs::FsPath& path);
void LoadAssocEntries(); void LoadAssocEntries();
auto FindFileAssocFor() -> std::vector<FileAssocEntry>; auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
void OnIndexChange();
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath { auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
return GetNewPath(m_path, entry.name); return GetNewPath(m_path, entry.name);
}; }
auto GetNewPath(u64 index) const -> fs::FsPath { auto GetNewPath(u64 index) const -> fs::FsPath {
return GetNewPath(m_path, GetEntry(index).name); return GetNewPath(m_path, GetEntry(index).name);
}; }
auto GetNewPathCurrent() const -> fs::FsPath { auto GetNewPathCurrent() const -> fs::FsPath {
return GetNewPath(m_index); return GetNewPath(m_index);
}; }
auto GetSelectedEntries() const -> std::vector<FileEntry> { auto GetSelectedEntries() const -> std::vector<FileEntry> {
if (!m_selected_count) { if (!m_selected_count) {

View File

@@ -12,10 +12,12 @@
#include "fs.hpp" #include "fs.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "ftpsrv_helper.hpp"
#include <nanovg_dk.h> #include <nanovg_dk.h>
#include <minIni.h> #include <minIni.h>
#include <pulsar.h> #include <pulsar.h>
#include <haze.h>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
@@ -200,7 +202,13 @@ auto GetNroIcon(const std::vector<u8>& nro_icon) -> std::vector<u8> {
return nro_icon; return nro_icon;
} }
void haze_callback(const HazeCallbackData *data) {
App::NotifyFlashLed();
evman::push(*data, false);
}
void nxlink_callback(const NxlinkCallbackData *data) { void nxlink_callback(const NxlinkCallbackData *data) {
App::NotifyFlashLed();
evman::push(*data, false); evman::push(*data, false);
} }
@@ -247,6 +255,9 @@ void App::Loop() {
} else if constexpr(std::is_same_v<T, evman::ExitEventData>) { } else if constexpr(std::is_same_v<T, evman::ExitEventData>) {
log_write("[ExitEventData] got event\n"); log_write("[ExitEventData] got event\n");
m_quit = true; 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>) { } else if constexpr(std::is_same_v<T, NxlinkCallbackData>) {
switch (arg.type) { switch (arg.type) {
case NxlinkCallbackType_Connected: case NxlinkCallbackType_Connected:
@@ -330,6 +341,28 @@ void App::NotifyClear(ui::NotifEntry::Side side) {
g_app->m_notif_manager.Clear(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> { auto App::GetThemeMetaList() -> std::span<ThemeMeta> {
return g_app->m_theme_meta_entries; return g_app->m_theme_meta_entries;
} }
@@ -383,6 +416,14 @@ auto App::GetThemeMusicEnable() -> bool {
return g_app->m_theme_music.Get(); 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 { auto App::GetLanguage() -> long {
return g_app->m_language.Get(); return g_app->m_language.Get();
} }
@@ -434,6 +475,28 @@ void App::SetThemeMusicEnable(bool enable) {
PlaySoundEffect(SoundEffect::SoundEffect_Music); 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) { void App::SetLanguage(long index) {
if (App::GetLanguage() != index) { if (App::GetLanguage() != index) {
g_app->m_language.Set(index); g_app->m_language.Set(index);
@@ -860,6 +923,14 @@ App::App(const char* argv0) {
log_write("hello world\n"); log_write("hello world\n");
} }
if (App::GetMtpEnable()) {
hazeInitialize(haze_callback);
}
if (App::GetFtpEnable()) {
ftpsrv::Init();
}
if (App::GetNxlinkEnable()) { if (App::GetNxlinkEnable()) {
nxlinkInitialize(nxlink_callback); 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()) { if (App::GetNxlinkEnable()) {
log_write("closing nxlink\n"); log_write("closing nxlink\n");
nxlinkExit(); nxlinkExit();

View File

@@ -298,7 +298,7 @@ auto DownloadInternal(CURL* curl, DataStruct& chunk, ProgressCallback pcallback,
fs::CreateDirectoryRecursivelyWithPath(&chunk.fs, tmp_buf); 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); log_write("failed to create file: %s\n", tmp_buf);
return false; return false;
} }

View File

@@ -134,7 +134,7 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
rc = CreateDirectory(path, ignore_read_only); 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); log_write("failed to create folder: %s\n", path);
return rc; return rc;
} }
@@ -166,7 +166,7 @@ Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path,
rc = CreateDirectory(path, ignore_read_only); 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); log_write("failed to create folder recursively: %s\n", path);
return rc; return rc;
} }
@@ -251,7 +251,7 @@ Result write_entire_file(FsFileSystem* _fs, const FsPath& path, const std::vecto
FsNative fs{_fs, false}; FsNative fs{_fs, false};
R_TRY(fs.GetFsOpenResult()); 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; return rc;
} }

View 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"

View File

@@ -61,6 +61,8 @@ void userAppInit(void) {
diagAbortWithResult(rc); diagAbortWithResult(rc);
if (R_FAILED(rc = setInitialize())) if (R_FAILED(rc = setInitialize()))
diagAbortWithResult(rc); diagAbortWithResult(rc);
if (R_FAILED(rc = hidsysInitialize()))
diagAbortWithResult(rc);
log_nxlink_init(); log_nxlink_init();
} }
@@ -68,6 +70,7 @@ void userAppInit(void) {
void userAppExit(void) { void userAppExit(void) {
log_nxlink_exit(); log_nxlink_exit();
hidsysExit();
setExit(); setExit();
accountExit(); accountExit();
nifmExit(); nifmExit();

View File

@@ -17,6 +17,7 @@
#include <errno.h> #include <errno.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <poll.h>
namespace { namespace {
@@ -114,7 +115,7 @@ auto recvall(int sock, void* buf, int size) -> bool {
if (errno != EWOULDBLOCK && errno != EAGAIN) { if (errno != EWOULDBLOCK && errno != EAGAIN) {
return false; return false;
} }
svcSleepThread(YieldType_WithoutCoreMigration); svcSleepThread(1e+6);
} else { } else {
got += len; got += len;
left -= len; left -= len;
@@ -132,7 +133,7 @@ auto sendall(Socket sock, const void* buf, int size) -> bool {
if (errno != EWOULDBLOCK && errno != EAGAIN) { if (errno != EWOULDBLOCK && errno != EAGAIN) {
return false; return false;
} }
svcSleepThread(YieldType_WithoutCoreMigration); svcSleepThread(1e+6);
} }
sent += len; sent += len;
left -= len; left -= len;
@@ -171,28 +172,6 @@ auto get_file_data(Socket sock, int max) -> std::vector<u8> {
return buf; 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) { void loop(void* args) {
log_write("in nxlink thread func\n"); log_write("in nxlink thread func\n");
const sockaddr_in servaddr{ const sockaddr_in servaddr{
@@ -266,21 +245,36 @@ void loop(void* args) {
sockaddr_in sa_remote{}; sockaddr_in sa_remote{};
while (!g_quit) { pollfd pfds[2];
svcSleepThread(1e+8); 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; break;
} }
char recvbuf[256]; if (pfds[1].revents & POLLIN) {
socklen_t from_len = sizeof(sa_remote); char recvbuf[6];
const auto udp_len = recvfrom(sock_udp, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&sa_remote, &from_len); socklen_t from_len = sizeof(sa_remote);
if (udp_len > 0 && !std::strncmp(recvbuf, UDP_MAGIC_SERVER, std::strlen(UDP_MAGIC_SERVER))) { auto udp_len = recvfrom(sock_udp, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&sa_remote, &from_len);
// log_write("got udp len: %d - %.*s\n", udp_len, udp_len, recvbuf); if (udp_len == sizeof(recvbuf) && !std::strncmp(recvbuf, UDP_MAGIC_SERVER, std::strlen(UDP_MAGIC_SERVER))) {
sa_remote.sin_family = AF_INET; // log_write("got udp len: %d - %.*s\n", udp_len, udp_len, recvbuf);
sa_remote.sin_port = htons(NXLINK_CLIENT_PORT); sa_remote.sin_family = AF_INET;
sendto(sock_udp, UDP_MAGIC_CLIENT, std::strlen(UDP_MAGIC_CLIENT), 0, (sockaddr*)&sa_remote, sizeof(sa_remote)); 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); socklen_t accept_len = sizeof(sa_remote);
@@ -316,8 +310,9 @@ void loop(void* args) {
} }
// check that we have enough space // check that we have enough space
fs::FsNativeSd fs;
s64 sd_storage_space_free; 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)); sendall(connfd, &ERR_SPACE, sizeof(ERR_SPACE));
continue; continue;
} }
@@ -345,16 +340,6 @@ void loop(void* args) {
path = name; 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 = create_directories(fs, path))) {
if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(path))) { if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(path))) {
sendall(connfd, &ERR_FILE, sizeof(ERR_FILE)); sendall(connfd, &ERR_FILE, sizeof(ERR_FILE));
@@ -364,7 +349,7 @@ void loop(void* args) {
// this is the path we will write to // this is the path we will write to
const auto temp_path = path + "~"; 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)); sendall(connfd, &ERR_FILE, sizeof(ERR_FILE));
log_write("failed to create file: %X\n", rc); log_write("failed to create file: %X\n", rc);
continue; 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); log_write("failed to delete %X\n", rc);
continue; continue;
} }
@@ -473,13 +458,14 @@ bool nxlinkInitialize(NxlinkCallback callback) {
g_callback = callback; g_callback = callback;
g_quit = false; g_quit = false;
if (R_FAILED(threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, 0x2C, 2))) { Result rc;
log_write("failed to create nxlink thread: 0x%X\n", socketGetLastResult()); 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; return false;
} }
if (R_FAILED(threadStart(&g_thread))) { if (R_FAILED(rc = threadStart(&g_thread))) {
log_write("failed to start nxlink thread: 0x%X\n", socketGetLastResult()); log_write("failed to start nxlink thread: 0x%X\n", rc);
threadClose(&g_thread); threadClose(&g_thread);
return false; return false;
} }

View File

@@ -548,7 +548,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
fs.CreateDirectoryRecursivelyWithPath(output, true); fs.CreateDirectoryRecursivelyWithPath(output, true);
Result rc; 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); log_write("failed to create file: %s 0x%04X\n", output, rc);
return false; return false;
} }

View File

@@ -30,7 +30,8 @@
#include <span> #include <span>
#include <utility> #include <utility>
#include <ranges> #include <ranges>
#include <stack> // #include <stack>
#include <expected>
namespace sphaira::ui::menu::filebrowser { namespace sphaira::ui::menu::filebrowser {
namespace { namespace {
@@ -61,42 +62,47 @@ constexpr std::string_view IMAGE_EXTENSIONS[] = {
"png", "jpg", "jpeg", "bmp", "gif", "png", "jpg", "jpeg", "bmp", "gif",
}; };
using PathPair = std::pair<std::string_view, std::string_view>; struct RomDatabaseEntry {
constexpr PathPair PATHS[]{ std::string_view folder;
PathPair{"3do", "The 3DO Company - 3DO"}, std::string_view database;
PathPair{"atari800", "Atari - 8-bit"}, };
PathPair{"atari2600", "Atari - 2600"},
PathPair{"atari5200", "Atari - 5200"}, // using PathPair = std::pair<std::string_view, std::string_view>;
PathPair{"atari7800", "Atari - 7800"}, constexpr RomDatabaseEntry PATHS[]{
PathPair{"atarilynx", "Atari - Lynx"}, { "3do", "The 3DO Company - 3DO"},
PathPair{"atarijaguar", "Atari - Jaguar"}, { "atari800", "Atari - 8-bit"},
PathPair{"atarijaguarcd", ""}, { "atari2600", "Atari - 2600"},
PathPair{"n3ds", "Nintendo - Nintendo 3DS"}, { "atari5200", "Atari - 5200"},
PathPair{"n64", "Nintendo - Nintendo 64"}, { "atari7800", "Atari - 7800"},
PathPair{"nds", "Nintendo - Nintendo DS"}, { "atarilynx", "Atari - Lynx"},
PathPair{"fds", "Nintendo - Famicom Disk System"}, { "atarijaguar", "Atari - Jaguar"},
PathPair{"nes", "Nintendo - Nintendo Entertainment System"}, { "atarijaguarcd", ""},
PathPair{"pokemini", "Nintendo - Pokemon Mini"}, { "n3ds", "Nintendo - Nintendo 3DS"},
PathPair{"gb", "Nintendo - Game Boy"}, { "n64", "Nintendo - Nintendo 64"},
PathPair{"gba", "Nintendo - Game Boy Advance"}, { "nds", "Nintendo - Nintendo DS"},
PathPair{"gbc", "Nintendo - Game Boy Color"}, { "fds", "Nintendo - Famicom Disk System"},
PathPair{"virtualboy", "Nintendo - Virtual Boy"}, { "nes", "Nintendo - Nintendo Entertainment System"},
PathPair{"gameandwatch", ""}, { "pokemini", "Nintendo - Pokemon Mini"},
PathPair{"sega32x", "Sega - 32X"}, { "gb", "Nintendo - Game Boy"},
PathPair{"segacd", "Sega - Mega CD - Sega CD"}, { "gba", "Nintendo - Game Boy Advance"},
PathPair{"dreamcast", "Sega - Dreamcast"}, { "gbc", "Nintendo - Game Boy Color"},
PathPair{"gamegear", "Sega - Game Gear"}, { "virtualboy", "Nintendo - Virtual Boy"},
PathPair{"genesis", "Sega - Mega Drive - Genesis"}, { "gameandwatch", ""},
PathPair{"mastersystem", "Sega - Master System - Mark III"}, { "sega32x", "Sega - 32X"},
PathPair{"megadrive", "Sega - Mega Drive - Genesis"}, { "segacd", "Sega - Mega CD - Sega CD"},
PathPair{"saturn", "Sega - Saturn"}, { "dreamcast", "Sega - Dreamcast"},
PathPair{"sg-1000", "Sega - SG-1000"}, { "gamegear", "Sega - Game Gear"},
PathPair{"psx", "Sony - PlayStation"}, { "genesis", "Sega - Mega Drive - Genesis"},
PathPair{"psp", "Sony - PlayStation Portable"}, { "mastersystem", "Sega - Master System - Mark III"},
PathPair{"snes", "Nintendo - Super Nintendo Entertainment System"}, { "megadrive", "Sega - Mega Drive - Genesis"},
PathPair{"pico8", "Sega - PICO"}, { "saturn", "Sega - Saturn"},
PathPair{"wonderswan", "Bandai - WonderSwan"}, { "sg-1000", "Sega - SG-1000"},
PathPair{"wonderswancolor", "Bandai - WonderSwan Color"}, { "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"}; 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 // tries to find database path using folder name
// names are taken from retropie // names are taken from retropie
// retroarch database names can also be used // 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) { if (path.length() <= 1) {
return {}; return -1;
} }
// this won't fail :) // this won't fail :)
const auto db_name = path.substr(path.find_last_of('/') + 1); 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 (( if ((
folder_name.length() == db_name.length() && !strncasecmp(folder_name.data(), db_name.data(), folder_name.length())) || p.folder.length() == db_name.length() && !strncasecmp(p.folder.data(), db_name.data(), p.folder.length())) ||
(database_name.length() == db_name.length() && !strncasecmp(database_name.data(), db_name.data(), database_name.length()))) { (p.database.length() == db_name.length() && !strncasecmp(p.database.data(), db_name.data(), p.database.length()))) {
log_write("found it :) %s\n", std::string{database_name.data(), database_name.length()}.c_str()); log_write("found it :) %.*s\n", p.database.length(), p.database.data());
return std::string{database_name.data(), database_name.length()}; return i;
} }
} }
// if we failed, try again but with the folder about // if we failed, try again but with the folder about
// "/roms/psx/scooby-doo/scooby-doo.bin", this will check psx // "/roms/psx/scooby-doo/scooby-doo.bin", this will check psx
const auto last_off = path.substr(0, path.find_last_of('/')); 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); const auto db_name2 = last_off.substr(off + 1);
printf("got db: %s\n", db_name2.c_str()); // printf("got db: %s\n", db_name2.c_str());
for (auto& [folder_name, database_name] : PATHS) { for (int i = 0; i < std::size(PATHS); i++) {
auto& p = PATHS[i];
if (( if ((
folder_name.length() == db_name2.length() && !strcasecmp(folder_name.data(), db_name2.data())) || p.folder.length() == db_name2.length() && !strcasecmp(p.folder.data(), db_name2.data())) ||
(database_name.length() == db_name2.length() && !strcasecmp(database_name.data(), db_name2.data()))) { (p.database.length() == db_name2.length() && !strcasecmp(p.database.data(), db_name2.data()))) {
log_write("found it :) %s\n", std::string{database_name.data(), database_name.length()}.c_str()); log_write("found it :) %.*s\n", p.database.length(), p.database.data());
return std::string{database_name.data(), database_name.length()}; 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 no db entries, use nro icon
if (database.empty()) { if (db_idx < 0) {
log_write("using nro image\n"); log_write("using nro image\n");
return nro_get_icon(nro.path, nro.icon_size, nro.icon_offset); 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_THUMBNAIL_PATH "/retroarch/thumbnails/"
#define RA_BOXART_EXT ".png" #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"; auto system_name_gh = system_name + "/master";
for (auto& c : system_name_gh) { for (auto& c : system_name_gh) {
if (c == ' ') { if (c == ' ') {
@@ -601,7 +609,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
} }
}, true)); }, 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](){ options->Add(std::make_shared<SidebarEntryCallback>("View as text (unfinished)"_i18n, [this](){
App::Push(std::make_shared<fileview::Menu>(GetNewPathCurrent())); App::Push(std::make_shared<fileview::Menu>(GetNewPathCurrent()));
}, true)); }, true));
@@ -864,7 +872,7 @@ void Menu::InstallForwarder() {
log_write("got filename2: %s\n\n", file_name.c_str()); 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{}; OwoConfig config{};
config.nro_path = assoc.path.toString(); 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 = nro.nacp.lang[0].name + std::string{" | "} + file_name;
// config.name = file_name; // config.name = file_name;
config.nacp = nro.nacp; 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)); 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> { auto Menu::FindFileAssocFor() -> std::vector<FileAssocEntry> {
// only support roms in correctly named folders, sorry! // 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& entry = GetEntry();
const auto extension = entry.internal_extension.empty() ? entry.extension : entry.internal_extension; const auto extension = entry.internal_extension.empty() ? entry.extension : entry.internal_extension;
if (extension.empty()) { 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 {}; 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; 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 // if database isn't empty, then we are in a valid folder
// search for an entry that matches the db and ext // search for an entry that matches the db and ext
for (const auto& assoc : m_assoc_entries) { for (const auto& assoc : m_assoc_entries) {
for (const auto& assoc_db : assoc.database) { 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) { for (const auto& assoc_ext : assoc.ext) {
if (assoc_ext == extension) { 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()); 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; return;
} }
ON_SCOPE_EXIT(closedir(dir)); ON_SCOPE_EXIT(closedir(dir));
fs::FsNativeSd fs;
while (auto d = readdir(dir)) { while (auto d = readdir(dir)) {
if (d->d_name[0] == '.') { if (d->d_name[0] == '.') {
continue; continue;
} }
// const std::string name = d->d_name;
const auto ext = std::strrchr(d->d_name, '.'); const auto ext = std::strrchr(d->d_name, '.');
if (!ext) { if (!ext || strcasecmp(ext, ".ini")) {
continue;
}
if (strcasecmp(ext, ".ini")) {
continue; continue;
} }
const auto full_path = GetNewPath(path, d->d_name); 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{}; FileAssocEntry assoc{};
for (int i = 0; i < ext_len; i++) { ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) {
for (int j = i; j < ext_len; j++) { auto assoc = static_cast<FileAssocEntry*>(UserData);
if (buf[j] == '|' || buf[j] == '\0') { if (!strcmp(Key, "path")) {
assoc.ext.emplace_back(buf + i, j - i); assoc->path = Value;
i += j - i; } else if (!strcmp(Key, "supported_extensions")) {
break; 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()) { if (assoc.ext.empty()) {
continue; continue;
} }
// assoc.name = name.substr(0, name.find_last_of('.'));
assoc.name.assign(d->d_name, ext - d->d_name); 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 // if path isn't empty, check if the file exists
bool file_exists{};
if (!assoc.path.empty()) { if (!assoc.path.empty()) {
file_exists = fs::FsNativeSd().FileExists(assoc.path); file_exists = fs.FileExists(assoc.path);
} else { } else {
log_write("\tpath is empty\n");
#if 1
const auto nro_name = assoc.name + ".nro"; const auto nro_name = assoc.name + ".nro";
for (const auto& nro : m_nro_entries) { for (const auto& nro : m_nro_entries) {
const auto len = std::strlen(nro.path); const auto len = std::strlen(nro.path);
@@ -1103,7 +1095,6 @@ void Menu::LoadAssocEntriesPath(const fs::FsPath& path) {
break; break;
} }
} }
#endif
} }
// after all of that, the file doesn't exist :( // after all of that, the file doesn't exist :(
@@ -1135,32 +1126,6 @@ void Menu::LoadAssocEntries() {
LoadAssocEntriesPath("/config/sphaira/assoc/"); 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() { void Menu::Sort() {
// returns true if lhs should be before rhs // returns true if lhs should be before rhs
const auto sort = m_sort.Get(); const auto sort = m_sort.Get();

View File

@@ -91,13 +91,13 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
Result rc; Result rc;
if (file_path[strlen(file_path) -1] == '/') { 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); log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
return false; return false;
} }
} else { } else {
Result rc; 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); log_write("failed to create file: %s 0x%04X\n", file_path, rc);
return false; return false;
} }
@@ -249,6 +249,14 @@ MainMenu::MainMenu() {
auto options = std::make_shared<Sidebar>("Network Options"_i18n, Sidebar::Side::LEFT); auto options = std::make_shared<Sidebar>("Network Options"_i18n, Sidebar::Side::LEFT);
ON_SCOPE_EXIT(App::Push(options)); 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){ options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [this](bool& enable){
App::SetNxlinkEnable(enable); App::SetNxlinkEnable(enable);
}, "Enabled"_i18n, "Disabled"_i18n)); }, "Enabled"_i18n, "Disabled"_i18n));

View File

@@ -369,7 +369,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
const auto file_path = fs::AppendPath(dir_path, name); const auto file_path = fs::AppendPath(dir_path, name);
Result rc; 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); log_write("failed to create file: %s 0x%04X\n", file_path, rc);
return false; return false;
} }

View File

@@ -135,6 +135,9 @@ auto NotifMananger::Clear(NotifEntry::Side side) -> void {
} }
auto NotifMananger::Clear() -> void { auto NotifMananger::Clear() -> void {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
m_entries_left.clear(); m_entries_left.clear();
m_entries_right.clear(); m_entries_right.clear();
} }