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]
|
[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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
[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
|
||||||
|
|||||||
@@ -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]
|
[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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
[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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
[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
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[config]
|
|
||||||
path=/retroarch/cores/pocketcdg_libretro_libnx.nro
|
|
||||||
supported_extensions=cdg
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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]
|
[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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
>;
|
>;
|
||||||
|
|||||||
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 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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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);
|
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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user