mirror of
https://github.com/joel16/NX-Shell.git
synced 2024-11-23 03:39:49 +00:00
fs: Refactor FS to use libc and add support for browsing USB devices via libusbhsfs
This commit is contained in:
parent
6f3d028927
commit
1107e604b3
3
.gitignore
vendored
3
.gitignore
vendored
@ -36,4 +36,5 @@
|
||||
*.elf
|
||||
*.nacp
|
||||
build/
|
||||
res/shaders
|
||||
res/shaders
|
||||
!libs/lib/libusbhsfs.a
|
||||
|
20
Makefile
20
Makefile
@ -47,28 +47,30 @@ ROMFS := res
|
||||
# Output folders for autogenerated files in romfs
|
||||
OUT_SHADERS := shaders
|
||||
|
||||
VERSION_MAJOR := 3
|
||||
VERSION_MINOR := 2
|
||||
VERSION_MAJOR := 4
|
||||
VERSION_MINOR := 0
|
||||
VERSION_MICRO := 0
|
||||
|
||||
APP_TITLE := NX-Shell
|
||||
APP_AUTHOR := Joel16
|
||||
APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}
|
||||
|
||||
EXT_LIBS := $(CURDIR)/libs
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections $(ARCH) $(DEFINES)
|
||||
CFLAGS := -g -Wall -O3 -ffunction-sections -Wno-restrict $(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
CFLAGS += `freetype-config --cflags`
|
||||
CFLAGS += -DVERSION_MAJOR=$(VERSION_MAJOR) -DVERSION_MINOR=$(VERSION_MINOR) -DVERSION_MICRO=$(VERSION_MICRO)
|
||||
CFLAGS += -DIMGUI_IMPL_OPENGL_LOADER_GLAD -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS -DIMGUI_DISABLE_DEMO_WINDOWS
|
||||
CFLAGS += -DIMGUI_DISABLE_DEBUG_TOOLS -DIMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
|
||||
CFLAGS += -DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS -DIMGUI_DISABLE_WIN32_FUNCTIONS
|
||||
CFLAGS += -DIMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -DIMGUI_ENABLE_FREETYPE
|
||||
CFLAGS += -DIMGUI_IMPL_OPENGL_LOADER_GLAD -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS -DIMGUI_DISABLE_DEMO_WINDOWS
|
||||
CFLAGS += -DIMGUI_DISABLE_DEBUG_TOOLS -DIMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
|
||||
CFLAGS += -DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS -DIMGUI_DISABLE_WIN32_FUNCTIONS
|
||||
CFLAGS += -DIMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -DIMGUI_ENABLE_FREETYPE
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -std=gnu++20 -fno-exceptions -fno-rtti
|
||||
|
||||
@ -76,13 +78,13 @@ ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := `curl-config --libs` `freetype-config --libs` -lgif -lturbojpeg -ljpeg -lpng -lwebp -ljansson \
|
||||
-lglad -lEGL -lglapi -ldrm_nouveau -lnx -lm -lz
|
||||
-lglad -lEGL -lglapi -ldrm_nouveau -lusbhsfs -lnx -lm -lz
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX) $(EXT_LIBS)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
@ -21,3 +21,4 @@ NX Shell is a multi-purpose file manager for the Nintendo Switch that aims towar
|
||||
- **Preetisketch** for the banner.
|
||||
- **Dear ImGui developers and contributors** for the GUI.
|
||||
- **devkitPro maintainers and contributors** for libnx, devkitA64, and many other packages used by this project.
|
||||
- **DarkMatterCore** for libusbhsfs.
|
||||
|
@ -10,7 +10,8 @@ typedef struct {
|
||||
} config_t;
|
||||
|
||||
extern config_t cfg;
|
||||
extern char cwd[FS_MAX_PATH];
|
||||
extern std::string cwd;
|
||||
extern std::string device;
|
||||
|
||||
namespace Config {
|
||||
int Save(config_t &config);
|
||||
|
@ -23,22 +23,24 @@ extern FsFileSystem *fs;
|
||||
extern FsFileSystem devices[FileSystemMax];
|
||||
|
||||
namespace FS {
|
||||
bool FileExists(const char path[FS_MAX_PATH]);
|
||||
bool DirExists(const char path[FS_MAX_PATH]);
|
||||
Result GetFileSize(const char path[FS_MAX_PATH], s64 &size);
|
||||
std::string GetFileExt(const std::string &filename);
|
||||
FileType GetFileType(const std::string &filename);
|
||||
Result GetDirList(const char path[FS_MAX_PATH], std::vector<FsDirectoryEntry> &entries);
|
||||
Result ChangeDirNext(const char path[FS_MAX_PATH], std::vector<FsDirectoryEntry> &entries);
|
||||
Result ChangeDirPrev(std::vector<FsDirectoryEntry> &entries);
|
||||
Result GetTimeStamp(FsDirectoryEntry &entry, FsTimeStampRaw ×tamp);
|
||||
Result Rename(FsDirectoryEntry &entry, const char filename[FS_MAX_PATH]);
|
||||
Result Delete(FsDirectoryEntry &entry);
|
||||
Result SetArchiveBit(FsDirectoryEntry &entry);
|
||||
bool FileExists(const std::string &path);
|
||||
bool DirExists(const std::string &path);
|
||||
bool GetFileSize(const std::string &path, std::size_t &size);
|
||||
bool GetDirList(const std::string &device, const std::string &path, std::vector<FsDirectoryEntry> &entries);
|
||||
bool ChangeDirNext(const std::string &path, std::vector<FsDirectoryEntry> &entries);
|
||||
bool ChangeDirPrev(std::vector<FsDirectoryEntry> &entries);
|
||||
bool GetTimeStamp(FsDirectoryEntry &entry, FsTimeStampRaw ×tamp);
|
||||
bool Rename(FsDirectoryEntry &entry, const std::string &dest_path);
|
||||
bool Delete(FsDirectoryEntry &entry);
|
||||
void Copy(FsDirectoryEntry &entry, const std::string &path);
|
||||
Result Paste(void);
|
||||
Result Move(void);
|
||||
bool Paste(void);
|
||||
bool Move(void);
|
||||
FileType GetFileType(const std::string &filename);
|
||||
Result SetArchiveBit(const std::string &path);
|
||||
Result GetFreeStorageSpace(s64 &size);
|
||||
Result GetTotalStorageSpace(s64 &size);
|
||||
Result GetUsedStorageSpace(s64 &size);
|
||||
std::string BuildPath(FsDirectoryEntry &entry);
|
||||
std::string BuildPath(const std::string &path, bool device_name);
|
||||
std::string GetFileExt(const std::string &filename);
|
||||
}
|
||||
|
@ -48,14 +48,12 @@ namespace Lang {
|
||||
SettingsTitle,
|
||||
SettingsSortTitle,
|
||||
SettingsLanguageTitle,
|
||||
SettingsUSBTitle,
|
||||
SettingsUSBUnmount,
|
||||
SettingsImageViewTitle,
|
||||
SettingsDevOptsTitle,
|
||||
SettingsAboutTitle,
|
||||
SettingsCheckForUpdates,
|
||||
SettingsSortNameAsc,
|
||||
SettingsSortNameDesc,
|
||||
SettingsSortSizeLarge,
|
||||
SettingsSortSizeSmall,
|
||||
SettingsImageViewFilenameToggle,
|
||||
SettingsDevOptsLogsToggle,
|
||||
SettingsAboutVersion,
|
||||
@ -71,6 +69,10 @@ namespace Lang {
|
||||
UpdateRestart,
|
||||
UpdateNotAvailable,
|
||||
|
||||
// USB Dialog
|
||||
USBUnmountPrompt,
|
||||
USBUnmountSuccess,
|
||||
|
||||
// Keyboard
|
||||
KeyboardEmpty,
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace Popups {
|
||||
void FilePropertiesPopup(WindowData &data);
|
||||
void ImageProperties(bool &state, Tex &texture);
|
||||
void OptionsPopup(WindowData &data);
|
||||
void ProgressPopup(float offset, float size, const std::string &title, const std::string &text);
|
||||
void UpdatePopup(bool &state, bool &connection_status, bool &available, const std::string &tag);
|
||||
void ProgressBar(float offset, float size, const std::string &title, const std::string &text);
|
||||
void USBPopup(bool &state);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ extern std::vector<Tex> file_icons;
|
||||
extern Tex folder_icon, check_icon, uncheck_icon;
|
||||
|
||||
namespace Textures {
|
||||
bool LoadImageFile(const char path[FS_MAX_PATH], std::vector<Tex> &textures);
|
||||
bool LoadImageFile(const std::string &path, std::vector<Tex> &textures);
|
||||
void Free(Tex &texture);
|
||||
void Init(void);
|
||||
void Exit(void);
|
||||
|
11
include/usb.hpp
Normal file
11
include/usb.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
|
||||
namespace USB {
|
||||
Result Init(void);
|
||||
void Exit(void);
|
||||
void Unmount(void);
|
||||
bool Connected(void);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
|
||||
@ -26,7 +27,8 @@ enum FS_SORT_STATE {
|
||||
typedef struct {
|
||||
std::vector<bool> checked;
|
||||
std::vector<bool> checked_copy;
|
||||
char cwd[FS_MAX_PATH + 1] = "";
|
||||
std::string cwd = "";
|
||||
std::string device = "";
|
||||
u64 count = 0;
|
||||
} WindowCheckboxData;
|
||||
|
||||
@ -44,9 +46,11 @@ typedef struct {
|
||||
|
||||
extern WindowData data;
|
||||
extern int sort;
|
||||
extern std::vector<std::string> devices_list;
|
||||
|
||||
namespace FileBrowser {
|
||||
bool Sort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB);
|
||||
bool TableSort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB);
|
||||
}
|
||||
|
||||
namespace ImageViewer {
|
||||
|
BIN
libs/lib/libusbhsfs.a
Normal file
BIN
libs/lib/libusbhsfs.a
Normal file
Binary file not shown.
126
libs/usbhsfs.h
Normal file
126
libs/usbhsfs.h
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* usbhsfs.h
|
||||
*
|
||||
* Copyright (c) 2020-2022, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
* Copyright (c) 2020-2021, XorTroll.
|
||||
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||
*
|
||||
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __USBHSFS_H__
|
||||
#define __USBHSFS_H__
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Library version.
|
||||
#define LIBUSBHSFS_VERSION_MAJOR 0
|
||||
#define LIBUSBHSFS_VERSION_MINOR 2
|
||||
#define LIBUSBHSFS_VERSION_MICRO 7
|
||||
|
||||
/// Helper macro to generate a string based on a filesystem type value.
|
||||
#define LIBUSBHSFS_FS_TYPE_STR(x) ((x) == UsbHsFsDeviceFileSystemType_FAT12 ? "FAT12" : ((x) == UsbHsFsDeviceFileSystemType_FAT16 ? "FAT16" : ((x) == UsbHsFsDeviceFileSystemType_FAT32 ? "FAT32" : \
|
||||
((x) == UsbHsFsDeviceFileSystemType_exFAT ? "exFAT" : ((x) == UsbHsFsDeviceFileSystemType_NTFS ? "NTFS" : ((x) == UsbHsFsDeviceFileSystemType_EXT2 ? "EXT2" : \
|
||||
((x) == UsbHsFsDeviceFileSystemType_EXT3 ? "EXT3" : ((x) == UsbHsFsDeviceFileSystemType_EXT4 ? "EXT4" : "Invalid"))))))))
|
||||
|
||||
/// Used to identify the filesystem type from a mounted filesystem (e.g. filesize limitations, etc.).
|
||||
typedef enum {
|
||||
UsbHsFsDeviceFileSystemType_Invalid = 0,
|
||||
UsbHsFsDeviceFileSystemType_FAT12 = 1,
|
||||
UsbHsFsDeviceFileSystemType_FAT16 = 2,
|
||||
UsbHsFsDeviceFileSystemType_FAT32 = 3,
|
||||
UsbHsFsDeviceFileSystemType_exFAT = 4,
|
||||
UsbHsFsDeviceFileSystemType_NTFS = 5, ///< Only returned by the GPL build of the library.
|
||||
UsbHsFsDeviceFileSystemType_EXT2 = 6, ///< Only returned by the GPL build of the library.
|
||||
UsbHsFsDeviceFileSystemType_EXT3 = 7, ///< Only returned by the GPL build of the library.
|
||||
UsbHsFsDeviceFileSystemType_EXT4 = 8 ///< Only returned by the GPL build of the library.
|
||||
} UsbHsFsDeviceFileSystemType;
|
||||
|
||||
/// Filesystem mount flags.
|
||||
/// Not all supported filesystems are compatible with these flags.
|
||||
/// The default mount bitmask is `UsbHsFsMountFlags_UpdateAccessTimes | UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ReplayJournal`.
|
||||
/// It can be overriden via usbHsFsSetFileSystemMountFlags() (see below).
|
||||
typedef enum {
|
||||
UsbHsFsMountFlags_None = 0x00000000, ///< No special action is taken.
|
||||
UsbHsFsMountFlags_IgnoreCaseSensitivity = 0x00000001, ///< NTFS only. Case sensitivity is ignored for all filesystem operations.
|
||||
UsbHsFsMountFlags_UpdateAccessTimes = 0x00000002, ///< NTFS only. File/directory access times are updated after each successful R/W operation.
|
||||
UsbHsFsMountFlags_ShowHiddenFiles = 0x00000004, ///< NTFS only. Hidden file entries are returned while enumerating directories.
|
||||
UsbHsFsMountFlags_ShowSystemFiles = 0x00000008, ///< NTFS only. System file entries are returned while enumerating directories.
|
||||
UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute = 0x00000010, ///< NTFS only. Allows writing to files even if they are marked as read-only.
|
||||
UsbHsFsMountFlags_ReadOnly = 0x00000100, ///< NTFS and EXT only. Filesystem is mounted as read-only.
|
||||
UsbHsFsMountFlags_ReplayJournal = 0x00000200, ///< NTFS and EXT only. Replays the log/journal to restore filesystem consistency (e.g. fix unsafe device ejections).
|
||||
UsbHsFsMountFlags_IgnoreHibernation = 0x00010000, ///< NTFS only. Filesystem is mounted even if it's in a hibernated state. The saved Windows session is completely lost.
|
||||
|
||||
///< Pre-generated bitmasks provided for convenience.
|
||||
UsbHsFsMountFlags_SuperUser = (UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ShowSystemFiles | UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute),
|
||||
UsbHsFsMountFlags_Force = (UsbHsFsMountFlags_ReplayJournal | UsbHsFsMountFlags_IgnoreHibernation)
|
||||
} UsbHsFsMountFlags;
|
||||
|
||||
/// Struct used to list mounted filesystems as devoptab devices.
|
||||
/// Everything but the manufacturer, product_name and name fields is empty/zeroed-out under SX OS.
|
||||
typedef struct {
|
||||
s32 usb_if_id; ///< USB interface ID. Internal use.
|
||||
u8 lun; ///< Logical unit. Internal use.
|
||||
u32 fs_idx; ///< Filesystem index. Internal use.
|
||||
bool write_protect; ///< Set to true if the logical unit is protected against write operations.
|
||||
u16 vid; ///< Vendor ID. Retrieved from the device descriptor. Useful if you wish to implement a filter in your application.
|
||||
u16 pid; ///< Product ID. Retrieved from the device descriptor. Useful if you wish to implement a filter in your application.
|
||||
char manufacturer[64]; ///< UTF-8 encoded manufacturer string. Retrieved from the device descriptor or SCSI Inquiry data. May be empty.
|
||||
char product_name[64]; ///< UTF-8 encoded product name string. Retrieved from the device descriptor or SCSI Inquiry data. May be empty.
|
||||
char serial_number[64]; ///< UTF-8 encoded serial number string. Retrieved from the device descriptor. May be empty.
|
||||
u64 capacity; ///< Raw capacity from the logical unit this filesystem belongs to. Use statvfs() to get the actual filesystem capacity. May be shared with other UsbHsFsDevice entries.
|
||||
char name[32]; ///< Mount name used by the devoptab virtual device interface (e.g. "ums0:"). Use it as a prefix in libcstd I/O calls to perform operations on this filesystem.
|
||||
u8 fs_type; ///< UsbHsFsDeviceFileSystemType.
|
||||
u32 flags; ///< UsbHsFsMountFlags bitmask used at mount time.
|
||||
} UsbHsFsDevice;
|
||||
|
||||
/// Initializes the USB Mass Storage Host interface.
|
||||
/// event_idx represents the event index to use with usbHsCreateInterfaceAvailableEvent() / usbHsDestroyInterfaceAvailableEvent(). Must be within the 0 - 2 range (inclusive).
|
||||
/// If you're not using any usb:hs interface available events on your own, set this value to 0. If running under SX OS, this value will be ignored.
|
||||
/// This function will fail if the deprecated fsp-usb service is running in the background.
|
||||
Result usbHsFsInitialize(u8 event_idx);
|
||||
|
||||
/// Closes the USB Mass Storage Host interface.
|
||||
/// If there are any UMS devices with mounted filesystems connected to the console when this function is called, their filesystems will be unmounted and their logical units will be stopped.
|
||||
void usbHsFsExit(void);
|
||||
|
||||
/// Returns a pointer to the user-mode status change event (with autoclear enabled).
|
||||
/// Useful to wait for USB Mass Storage status changes without having to constantly poll the interface.
|
||||
/// Returns NULL if the USB Mass Storage Host interface hasn't been initialized.
|
||||
UEvent *usbHsFsGetStatusChangeUserEvent(void);
|
||||
|
||||
/// Returns the mounted device count.
|
||||
u32 usbHsFsGetMountedDeviceCount(void);
|
||||
|
||||
/// Lists up to max_count mounted devices and stores their information in the provided UsbHsFsDevice array.
|
||||
/// Returns the total number of written entries.
|
||||
u32 usbHsFsListMountedDevices(UsbHsFsDevice *out, u32 max_count);
|
||||
|
||||
/// Unmounts all filesystems from the UMS device with a USB interface ID that matches the one from the provided UsbHsFsDevice, and stops all of its logical units.
|
||||
/// Can be used to safely unmount a UMS device at runtime, if that's needed for some reason. Calling this function before usbHsFsExit() isn't necessary.
|
||||
/// If multiple UsbHsFsDevice entries are returned for the same UMS device, any of them can be used as the input argument for this function.
|
||||
/// If successful, and signal_status_event is true, this will also fire the user-mode status change event from usbHsFsGetStatusChangeUserEvent().
|
||||
/// This function has no effect at all under SX OS.
|
||||
bool usbHsFsUnmountDevice(UsbHsFsDevice *device, bool signal_status_event);
|
||||
|
||||
/// Returns a bitmask with the current filesystem mount flags.
|
||||
/// Can be used even if the USB Mass Storage Host interface hasn't been initialized.
|
||||
/// This function has no effect at all under SX OS.
|
||||
u32 usbHsFsGetFileSystemMountFlags(void);
|
||||
|
||||
/// Takes an input bitmask with the desired filesystem mount flags, which will be used for all mount operations.
|
||||
/// Can be used even if the USB Mass Storage Host interface hasn't been initialized.
|
||||
/// This function has no effect at all under SX OS.
|
||||
void usbHsFsSetFileSystemMountFlags(u32 flags);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __USBHSFS_H__ */
|
@ -11,6 +11,7 @@
|
||||
config_t cfg;
|
||||
|
||||
namespace Config {
|
||||
static const char *config_path = "/switch/NX-Shell/config.json";
|
||||
static const char *config_file = "{\n\t\"config_version\": %d,\n\t\"language\": %d,\n\t\"dev_options\": %d,\n\t\"image_filename\": %d\n}";
|
||||
static int config_version_holder = 0;
|
||||
static const int buf_size = 128;
|
||||
@ -21,18 +22,18 @@ namespace Config {
|
||||
u64 len = std::snprintf(buf, buf_size, config_file, CONFIG_VERSION, config.lang, config.dev_options, config.image_filename);
|
||||
|
||||
// Delete and re-create the file, we don't care about the return value here.
|
||||
fsFsDeleteFile(fs, "/switch/NX-Shell/config.json");
|
||||
fsFsCreateFile(fs, "/switch/NX-Shell/config.json", len, 0);
|
||||
fsFsDeleteFile(std::addressof(devices[FileSystemSDMC]), config_path);
|
||||
fsFsCreateFile(std::addressof(devices[FileSystemSDMC]), config_path, len, 0);
|
||||
|
||||
FsFile file;
|
||||
if (R_FAILED(ret = fsFsOpenFile(fs, "/switch/NX-Shell/config.json", FsOpenMode_Write, std::addressof(file)))) {
|
||||
Log::Error("fsFsOpenFile(/switch/NX-Shell/config.json) failed: 0x%x\n", ret);
|
||||
if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), config_path, FsOpenMode_Write, std::addressof(file)))) {
|
||||
Log::Error("Config::Save fsFsOpenFile(%s) failed: 0x%x\n", config_path, ret);
|
||||
delete[] buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (R_FAILED(ret = fsFileWrite(std::addressof(file), 0, buf, len, FsWriteOption_Flush))) {
|
||||
Log::Error("fsFileWrite(/switch/NX-Shell/config.json) failed: 0x%x\n", ret);
|
||||
Log::Error("Config::Save fsFileWrite(%s) failed: 0x%x\n", config_path, ret);
|
||||
delete[] buf;
|
||||
fsFileClose(std::addressof(file));
|
||||
return ret;
|
||||
@ -53,17 +54,17 @@ namespace Config {
|
||||
Result ret = 0;
|
||||
|
||||
if (!FS::DirExists("/switch/"))
|
||||
fsFsCreateDirectory(fs, "/switch");
|
||||
fsFsCreateDirectory(std::addressof(devices[FileSystemSDMC]), "/switch");
|
||||
if (!FS::DirExists("/switch/NX-Shell/"))
|
||||
fsFsCreateDirectory(fs, "/switch/NX-Shell");
|
||||
fsFsCreateDirectory(std::addressof(devices[FileSystemSDMC]), "/switch/NX-Shell");
|
||||
|
||||
if (!FS::FileExists("/switch/NX-Shell/config.json")) {
|
||||
if (!FS::FileExists(config_path)) {
|
||||
Config::SetDefault(cfg);
|
||||
return Config::Save(cfg);
|
||||
}
|
||||
|
||||
FsFile file;
|
||||
if (R_FAILED(ret = fsFsOpenFile(fs, "/switch/NX-Shell/config.json", FsOpenMode_Read, std::addressof(file))))
|
||||
if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), config_path, FsOpenMode_Read, std::addressof(file))))
|
||||
return ret;
|
||||
|
||||
s64 size = 0;
|
||||
@ -96,7 +97,7 @@ namespace Config {
|
||||
|
||||
// Delete config file if config file is updated. This will rarely happen.
|
||||
if (config_version_holder < CONFIG_VERSION) {
|
||||
fsFsDeleteFile(fs, "/switch/NX-Shell/config.json");
|
||||
fsFsDeleteFile(std::addressof(devices[FileSystemSDMC]), config_path);
|
||||
Config::SetDefault(cfg);
|
||||
return Config::Save(cfg);
|
||||
}
|
||||
|
721
source/fs.cpp
721
source/fs.cpp
@ -1,6 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "config.hpp"
|
||||
@ -12,64 +13,331 @@
|
||||
// Global vars
|
||||
FsFileSystem *fs;
|
||||
FsFileSystem devices[FileSystemMax];
|
||||
char cwd[FS_MAX_PATH] = "/";
|
||||
std::string cwd = "/";
|
||||
std::string device = "sdmc:";
|
||||
|
||||
namespace FS {
|
||||
static int PREVIOUS_BROWSE_STATE = 0;
|
||||
|
||||
typedef struct {
|
||||
char path[FS_MAX_PATH];
|
||||
char filename[FS_MAX_PATH];
|
||||
std::string path;
|
||||
std::string filename;
|
||||
bool is_directory = false;
|
||||
} FSCopyEntry;
|
||||
|
||||
FSCopyEntry fs_copy_entry;
|
||||
|
||||
bool FileExists(const char path[FS_MAX_PATH]) {
|
||||
FsFile file;
|
||||
if (R_SUCCEEDED(fsFsOpenFile(fs, path, FsOpenMode_Read, std::addressof(file)))) {
|
||||
fsFileClose(std::addressof(file));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
bool FileExists(const std::string &path) {
|
||||
struct stat file_stat = { 0 };
|
||||
return (stat(path.c_str(), std::addressof(file_stat)) == 0 && S_ISREG(file_stat.st_mode));
|
||||
}
|
||||
|
||||
bool DirExists(const char path[FS_MAX_PATH]) {
|
||||
FsDir dir;
|
||||
if (R_SUCCEEDED(fsFsOpenDirectory(fs, path, FsDirOpenMode_ReadDirs, std::addressof(dir)))) {
|
||||
fsDirClose(std::addressof(dir));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
bool DirExists(const std::string &path) {
|
||||
struct stat dir_stat = { 0 };
|
||||
return (stat(path.c_str(), &dir_stat) == 0);
|
||||
}
|
||||
|
||||
Result GetFileSize(const char path[FS_MAX_PATH], s64 &size) {
|
||||
Result ret = 0;
|
||||
bool GetFileSize(const std::string &path, std::size_t &size) {
|
||||
struct stat file_stat = { 0 };
|
||||
std::string full_path = FS::BuildPath(path, true);
|
||||
|
||||
if (stat(full_path.c_str(), std::addressof(file_stat)) != 0) {
|
||||
Log::Error("FS::GetFileSize(%s) failed to stat file.\n", full_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
size = file_stat.st_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetDirList(const std::string &device, const std::string &path, std::vector<FsDirectoryEntry> &entries) {
|
||||
DIR *dir = nullptr;
|
||||
struct dirent *d_entry = nullptr;
|
||||
std::string full_path = device + path;
|
||||
dir = opendir(full_path.c_str());
|
||||
entries.clear();
|
||||
|
||||
if (dir) {
|
||||
FsDirectoryEntry parent_entry = { 0 };
|
||||
std::strncpy(parent_entry.name, "..", 3);
|
||||
parent_entry.type = FsDirEntryType_Dir;
|
||||
entries.push_back(parent_entry);
|
||||
|
||||
while((d_entry = readdir(dir))) {
|
||||
FsDirectoryEntry entry = { 0 };
|
||||
|
||||
std::strncpy(entry.name, d_entry->d_name, FS_MAX_PATH);
|
||||
entry.type = (d_entry->d_type & DT_DIR)? FsDirEntryType_Dir : FsDirEntryType_File;
|
||||
|
||||
if (entry.type == FsDirEntryType_File) {
|
||||
std::string path;
|
||||
struct stat file_stat = { 0 };
|
||||
entry.file_size = 0;
|
||||
|
||||
std::string file_path = full_path;
|
||||
file_path.append(cwd.compare("/") == 0? "" : "/");
|
||||
file_path.append(entry.name);
|
||||
|
||||
if (!stat(file_path.c_str(), std::addressof(file_stat)))
|
||||
entry.file_size = file_stat.st_size;
|
||||
}
|
||||
|
||||
entries.push_back(entry);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
else {
|
||||
Log::Error("FS::GetDirList(%s) to open path.\n", full_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ChangeDir(const std::string &path, std::vector<FsDirectoryEntry> &entries) {
|
||||
std::vector<FsDirectoryEntry> new_entries;
|
||||
const std::string new_path = path;
|
||||
cwd = path;
|
||||
|
||||
FsFile file;
|
||||
if (R_FAILED(ret = fsFsOpenFile(fs, path, FsOpenMode_Read, std::addressof(file)))) {
|
||||
Log::Error("fsFsOpenFile(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
bool ret = FS::GetDirList(device, new_path, new_entries);
|
||||
|
||||
entries.clear();
|
||||
entries = new_entries;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ChangeDirNext(const std::string &path, std::vector<FsDirectoryEntry> &entries) {
|
||||
return FS::ChangeDir(FS::BuildPath(path, false), entries);
|
||||
}
|
||||
|
||||
bool ChangeDirPrev(std::vector<FsDirectoryEntry> &entries) {
|
||||
// We are already at the root.
|
||||
if (cwd.compare("/") == 0)
|
||||
return false;
|
||||
|
||||
std::filesystem::path path = cwd;
|
||||
std::string parent_path = path.parent_path();
|
||||
return FS::ChangeDir(parent_path.empty()? cwd : parent_path, entries);
|
||||
}
|
||||
|
||||
bool GetTimeStamp(FsDirectoryEntry &entry, FsTimeStampRaw ×tamp) {
|
||||
struct stat file_stat = { 0 };
|
||||
std::string full_path = FS::BuildPath(entry);
|
||||
|
||||
if (stat(full_path.c_str(), std::addressof(file_stat)) != 0) {
|
||||
Log::Error("FS::GetTimeStamp(%s) failed to stat file.\n", full_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp.is_valid = 1;
|
||||
timestamp.created = file_stat.st_ctime;
|
||||
timestamp.modified = file_stat.st_mtime;
|
||||
timestamp.accessed = file_stat.st_atime;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Rename(FsDirectoryEntry &entry, const std::string &dest_path) {
|
||||
std::string src_path = FS::BuildPath(entry);
|
||||
|
||||
if (rename(src_path.c_str(), dest_path.c_str()) != 0) {
|
||||
Log::Error("FS::Rename(%s, %s) failed.\n", src_path.c_str(), dest_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(ret = fsFileGetSize(std::addressof(file), std::addressof(size)))) {
|
||||
Log::Error("fsFileGetSize(%s) failed: 0x%x\n", path, ret);
|
||||
fsFileClose(std::addressof(file));
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteRecursive(const std::string &path) {
|
||||
DIR *dir = nullptr;
|
||||
struct dirent *entry = nullptr;
|
||||
dir = opendir(path.c_str());
|
||||
|
||||
if (dir) {
|
||||
while((entry = readdir(dir))) {
|
||||
std::string filename = entry->d_name;
|
||||
if ((filename.compare(".") == 0) || (filename.compare("..") == 0))
|
||||
continue;
|
||||
|
||||
std::string file_path = path;
|
||||
file_path.append(cwd.compare("/") == 0? "" : "/");
|
||||
file_path.append(filename);
|
||||
|
||||
if (entry->d_type & DT_DIR) {
|
||||
FS::DeleteRecursive(file_path);
|
||||
}
|
||||
else {
|
||||
if (remove(file_path.c_str()) != 0) {
|
||||
Log::Error("FS::DeleteRecursive(%s) failed to delete file.\n", file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
else {
|
||||
Log::Error("FS::DeleteRecursive(%s) failed to open path.\n", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return (rmdir(path.c_str()) == 0);
|
||||
}
|
||||
|
||||
bool Delete(FsDirectoryEntry &entry) {
|
||||
std::string full_path = FS::BuildPath(entry);
|
||||
|
||||
if (entry.type == FsDirEntryType_Dir) {
|
||||
if (!FS::DeleteRecursive(full_path)) {
|
||||
Log::Error("FS::Delete(%s) failed to delete folder.\n", full_path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (remove(full_path.c_str()) != 0) {
|
||||
Log::Error("FS::Delete(%s) failed to delete file.\n", full_path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fsFileClose(std::addressof(file));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CopyFile(const std::string &src_path, const std::string &dest_path) {
|
||||
FILE *src = fopen(src_path.c_str(), "rb");
|
||||
if (!src) {
|
||||
Log::Error("FS::CopyFile (%s) failed to open src file.\n", src_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat file_stat = { 0 };
|
||||
if (stat(src_path.c_str(), std::addressof(file_stat)) != 0) {
|
||||
Log::Error("FS::CopyFile (%s) failed to get src file size.\n", src_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = file_stat.st_size;
|
||||
|
||||
FILE *dest = fopen(dest_path.c_str(), "wb");
|
||||
if (!dest) {
|
||||
Log::Error("FS::CopyFile (%s) failed to open dest file.\n", dest_path.c_str());
|
||||
fclose(src);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t bytes_read = 0, offset = 0;
|
||||
const std::size_t buf_size = 0x10000;
|
||||
unsigned char *buf = new unsigned char[buf_size];
|
||||
std::string filename = std::filesystem::path(src_path).filename();
|
||||
|
||||
do {
|
||||
std::memset(buf, 0, buf_size);
|
||||
|
||||
bytes_read = fread(buf, sizeof(unsigned char), buf_size, src);
|
||||
if (bytes_read < 0) {
|
||||
Log::Error("FS::CopyFile (%s) failed to read src file.\n", src_path.c_str());
|
||||
delete[] buf;
|
||||
fclose(src);
|
||||
fclose(dest);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t bytes_written = fwrite(buf, sizeof(unsigned char), bytes_read, dest);
|
||||
if (bytes_written != bytes_read) {
|
||||
Log::Error("FS::CopyFile (%s) failed to write to dest file.\n", dest_path.c_str());
|
||||
delete[] buf;
|
||||
fclose(src);
|
||||
fclose(dest);
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += bytes_read;
|
||||
Popups::ProgressBar(static_cast<float>(offset), static_cast<float>(size), strings[cfg.lang][Lang::OptionsCopying], filename.c_str());
|
||||
} while (offset < size);
|
||||
|
||||
delete[] buf;
|
||||
fclose(src);
|
||||
fclose(dest);
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string GetFileExt(const std::string &filename) {
|
||||
std::string ext = std::filesystem::path(filename).extension();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::toupper);
|
||||
return ext;
|
||||
|
||||
static bool CopyDir(const std::string &src_path, const std::string &dest_path) {
|
||||
DIR *dir = nullptr;
|
||||
struct dirent *entry = nullptr;
|
||||
dir = opendir(src_path.c_str());
|
||||
|
||||
if (dir) {
|
||||
// This may fail or not, but we don't care -> make the dir if it doesn't exist, otherwise continue.
|
||||
mkdir(dest_path.c_str(), 0700);
|
||||
|
||||
while((entry = readdir(dir))) {
|
||||
std::string filename = entry->d_name;
|
||||
if ((filename.compare(".") == 0) || (filename.compare("..") == 0))
|
||||
continue;
|
||||
|
||||
std::string src = src_path;
|
||||
src.append("/");
|
||||
src.append(filename);
|
||||
|
||||
std::string dest = dest_path;
|
||||
dest.append("/");
|
||||
dest.append(filename);
|
||||
|
||||
if (entry->d_type & DT_DIR)
|
||||
FS::CopyDir(src.c_str(), dest.c_str()); // Copy Folder (via recursion)
|
||||
else
|
||||
FS::CopyFile(src.c_str(), dest.c_str()); // Copy File
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
else {
|
||||
Log::Error("FS::CopyDir(%s) failed to open path.\n", src_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Copy(FsDirectoryEntry &entry, const std::string &path) {
|
||||
std::string full_path = path;
|
||||
full_path.append(path.compare("/") == 0? "" : "/");
|
||||
full_path.append(entry.name);
|
||||
|
||||
if ((std::strncmp(entry.name, "..", 2)) != 0) {
|
||||
fs_copy_entry.path = full_path;
|
||||
fs_copy_entry.filename = entry.name;
|
||||
|
||||
if (entry.type == FsDirEntryType_Dir)
|
||||
fs_copy_entry.is_directory = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Paste(void) {
|
||||
bool ret = false;
|
||||
std::string path = FS::BuildPath(fs_copy_entry.filename, true);
|
||||
|
||||
if (fs_copy_entry.is_directory)
|
||||
ret = FS::CopyDir(fs_copy_entry.path, path);
|
||||
else
|
||||
ret = FS::CopyFile(fs_copy_entry.path, path);
|
||||
|
||||
fs_copy_entry = {};
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Move(void) {
|
||||
std::string path = FS::BuildPath(fs_copy_entry.filename, true);
|
||||
|
||||
if (rename(fs_copy_entry.path.c_str(), path.c_str()) != 0) {
|
||||
Log::Error("FS::Move(%s, %s) failed.\n", fs_copy_entry.path.c_str(), path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fs_copy_entry = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
FileType GetFileType(const std::string &filename) {
|
||||
std::string ext = FS::GetFileExt(filename);
|
||||
|
||||
@ -84,358 +352,17 @@ namespace FS {
|
||||
return FileTypeNone;
|
||||
}
|
||||
|
||||
Result GetDirList(const char path[FS_MAX_PATH], std::vector<FsDirectoryEntry> &entries) {
|
||||
FsDir dir;
|
||||
Result SetArchiveBit(const std::string &path) {
|
||||
Result ret = 0;
|
||||
s64 read_entries = 0;
|
||||
const std::string cwd = path;
|
||||
entries.clear();
|
||||
|
||||
// Create ".." entry
|
||||
FsDirectoryEntry entry;
|
||||
std::strncpy(entry.name, "..", 3);
|
||||
entry.type = FsDirEntryType_Dir;
|
||||
entry.file_size = 0;
|
||||
entries.push_back(entry);
|
||||
|
||||
if (R_FAILED(ret = fsFsOpenDirectory(fs, path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)))) {
|
||||
Log::Error("GetDirListfsFsOpenDirectory(%s) failed: 0x%x\n", path, ret);
|
||||
char fs_path[FS_MAX_PATH];
|
||||
std::snprintf(fs_path, FS_MAX_PATH, path.c_str());
|
||||
|
||||
if (R_FAILED(ret = fsFsSetConcatenationFileAttribute(std::addressof(devices[FileSystemSDMC]), fs_path))) {
|
||||
Log::Error("fsFsSetConcatenationFileAttribute(%s) failed: 0x%x\n", path.c_str(), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
FsDirectoryEntry entry;
|
||||
if (R_FAILED(ret = fsDirRead(std::addressof(dir), &read_entries, 1, std::addressof(entry)))) {
|
||||
fsDirClose(std::addressof(dir));
|
||||
Log::Error("fsDirRead(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (read_entries != 1)
|
||||
break;
|
||||
|
||||
entries.push_back(entry);
|
||||
}
|
||||
|
||||
fsDirClose(std::addressof(dir));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Result ChangeDir(const char path[FS_MAX_PATH], std::vector<FsDirectoryEntry> &entries) {
|
||||
Result ret = 0;
|
||||
std::vector<FsDirectoryEntry> new_entries;
|
||||
|
||||
if (R_FAILED(ret = FS::GetDirList(path, new_entries)))
|
||||
return ret;
|
||||
|
||||
// Apply cd after successfully listing new directory
|
||||
entries.clear();
|
||||
std::strncpy(cwd, path, FS_MAX_PATH - 1);
|
||||
Config::Save(cfg);
|
||||
entries = new_entries;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int GetPrevPath(char path[FS_MAX_PATH]) {
|
||||
if (std::strlen(cwd) <= 1 && cwd[0] == '/')
|
||||
return -1;
|
||||
|
||||
// Remove upmost directory
|
||||
bool copy = false;
|
||||
int len = 0;
|
||||
for (ssize_t i = std::strlen(cwd); i >= 0; i--) {
|
||||
if (cwd[i] == '/')
|
||||
copy = true;
|
||||
if (copy) {
|
||||
path[i] = cwd[i];
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
// remove trailing slash
|
||||
if (len > 1 && path[len - 1] == '/')
|
||||
len--;
|
||||
|
||||
path[len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result ChangeDirNext(const char path[FS_MAX_PATH], std::vector<FsDirectoryEntry> &entries) {
|
||||
char new_cwd[FS_MAX_PATH];
|
||||
const char *sep = (std::strncmp(cwd, "/", 2) == 0)? "" : "/"; // Don't append / if at /
|
||||
|
||||
if ((std::snprintf(new_cwd, FS_MAX_PATH, "%s%s%s", cwd, sep, path)) > 0)
|
||||
return FS::ChangeDir(new_cwd, entries);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result ChangeDirPrev(std::vector<FsDirectoryEntry> &entries) {
|
||||
char new_cwd[FS_MAX_PATH];
|
||||
if (FS::GetPrevPath(new_cwd) < 0)
|
||||
return -1;
|
||||
|
||||
return FS::ChangeDir(new_cwd, entries);
|
||||
}
|
||||
|
||||
static int BuildPath(FsDirectoryEntry &entry, char path[FS_MAX_PATH]) {
|
||||
if ((std::snprintf(path, FS_MAX_PATH, "%s%s%s", cwd, (std::strncmp(cwd, "/", 2) == 0)? "" : "/", entry.name)) > 0)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int BuildPath(char path[FS_MAX_PATH], const char filename[FS_MAX_PATH]) {
|
||||
if ((std::snprintf(path, FS_MAX_PATH, "%s%s%s", cwd, (std::strncmp(cwd, "/", 2) == 0)? "" : "/", filename[0] != '\0'? filename : "")) > 0)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Result GetTimeStamp(FsDirectoryEntry &entry, FsTimeStampRaw ×tamp) {
|
||||
Result ret = 0;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (R_FAILED(FS::BuildPath(entry, path)))
|
||||
return -1;
|
||||
|
||||
if (R_FAILED(ret = fsFsGetFileTimeStampRaw(fs, path, std::addressof(timestamp)))) {
|
||||
Log::Error("fsFsGetFileTimeStampRaw(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result Rename(FsDirectoryEntry &entry, const char filename[FS_MAX_PATH]) {
|
||||
Result ret = 0;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (FS::BuildPath(entry, path) < 0)
|
||||
return -1;
|
||||
|
||||
char new_path[FS_MAX_PATH];
|
||||
if (FS::BuildPath(new_path, filename) < 0)
|
||||
return -1;
|
||||
|
||||
if (entry.type == FsDirEntryType_Dir) {
|
||||
if (R_FAILED(ret = fsFsRenameDirectory(fs, path, new_path))) {
|
||||
Log::Error("fsFsRenameDirectory(%s, %s) failed: 0x%x\n", path, new_path, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (R_FAILED(ret = fsFsRenameFile(fs, path, new_path))) {
|
||||
Log::Error("fsFsRenameFile(%s, %s) failed: 0x%x\n", path, new_path, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result Delete(FsDirectoryEntry &entry) {
|
||||
Result ret = 0;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (FS::BuildPath(entry, path) < 0)
|
||||
return -1;
|
||||
|
||||
if (entry.type == FsDirEntryType_Dir) {
|
||||
if (R_FAILED(ret = fsFsDeleteDirectoryRecursively(fs, path))) {
|
||||
Log::Error("fsFsDeleteDirectoryRecursively(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (R_FAILED(ret = fsFsDeleteFile(fs, path))) {
|
||||
Log::Error("fsFsDeleteFile(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result SetArchiveBit(FsDirectoryEntry &entry) {
|
||||
Result ret = 0;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
if (FS::BuildPath(entry, path) < 0)
|
||||
return -1;
|
||||
|
||||
if (R_FAILED(ret = fsFsSetConcatenationFileAttribute(fs, path))) {
|
||||
Log::Error("fsFsSetConcatenationFileAttribute(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Result CopyFile(const char src_path[FS_MAX_PATH], const char dest_path[FS_MAX_PATH]) {
|
||||
Result ret = 0;
|
||||
FsFile src_handle, dest_handle;
|
||||
|
||||
if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[PREVIOUS_BROWSE_STATE]), src_path, FsOpenMode_Read, std::addressof(src_handle)))) {
|
||||
Log::Error("fsFsOpenFile(%s) failed: 0x%x\n", src_path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s64 size = 0;
|
||||
if (R_FAILED(ret = fsFileGetSize(std::addressof(src_handle), std::addressof(size)))) {
|
||||
Log::Error("fsFileGetSize(%s) failed: 0x%x\n", src_path, ret);
|
||||
fsFileClose(std::addressof(src_handle));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This may fail or not, but we don't care -> create the file if it doesn't exist, otherwise continue.
|
||||
fsFsCreateFile(fs, dest_path, size, 0);
|
||||
|
||||
if (R_FAILED(ret = fsFsOpenFile(fs, dest_path, FsOpenMode_Write, std::addressof(dest_handle)))) {
|
||||
Log::Error("fsFsOpenFile(%s) failed: 0x%x\n", dest_path, ret);
|
||||
fsFileClose(std::addressof(src_handle));
|
||||
return ret;
|
||||
}
|
||||
|
||||
u64 bytes_read = 0;
|
||||
const u64 buf_size = 0x10000;
|
||||
s64 offset = 0;
|
||||
unsigned char *buf = new unsigned char[buf_size];
|
||||
std::string filename = std::filesystem::path(src_path).filename();
|
||||
|
||||
do {
|
||||
std::memset(buf, 0, buf_size);
|
||||
|
||||
if (R_FAILED(ret = fsFileRead(std::addressof(src_handle), offset, buf, buf_size, FsReadOption_None, std::addressof(bytes_read)))) {
|
||||
Log::Error("fsFileRead(%s) failed: 0x%x\n", src_path, ret);
|
||||
delete[] buf;
|
||||
fsFileClose(std::addressof(src_handle));
|
||||
fsFileClose(std::addressof(dest_handle));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (R_FAILED(ret = fsFileWrite(std::addressof(dest_handle), offset, buf, bytes_read, FsWriteOption_Flush))) {
|
||||
Log::Error("fsFileWrite(%s) failed: 0x%x\n", dest_path, ret);
|
||||
delete[] buf;
|
||||
fsFileClose(std::addressof(src_handle));
|
||||
fsFileClose(std::addressof(dest_handle));
|
||||
return ret;
|
||||
}
|
||||
|
||||
offset += bytes_read;
|
||||
Popups::ProgressBar(static_cast<float>(offset), static_cast<float>(size), strings[cfg.lang][Lang::OptionsCopying], filename.c_str());
|
||||
} while(offset < size);
|
||||
|
||||
delete[] buf;
|
||||
fsFileClose(std::addressof(src_handle));
|
||||
fsFileClose(std::addressof(dest_handle));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Result CopyDir(const char src_path[FS_MAX_PATH], const char dest_path[FS_MAX_PATH]) {
|
||||
Result ret = 0;
|
||||
FsDir dir;
|
||||
|
||||
if (R_FAILED(ret = fsFsOpenDirectory(std::addressof(devices[PREVIOUS_BROWSE_STATE]), src_path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)))) {
|
||||
Log::Error("fsFsOpenDirectory(%s) failed: 0x%x\n", src_path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This may fail or not, but we don't care -> make the dir if it doesn't exist, otherwise continue.
|
||||
fsFsCreateDirectory(fs, dest_path);
|
||||
|
||||
s64 entry_count = 0;
|
||||
if (R_FAILED(ret = fsDirGetEntryCount(std::addressof(dir), std::addressof(entry_count)))) {
|
||||
Log::Error("fsDirGetEntryCount(%s) failed: 0x%x\n", src_path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
FsDirectoryEntry *entries = new FsDirectoryEntry[entry_count * sizeof(*entries)];
|
||||
if (R_FAILED(ret = fsDirRead(std::addressof(dir), nullptr, static_cast<size_t>(entry_count), entries))) {
|
||||
Log::Error("fsDirRead(%s) failed: 0x%x\n", src_path, ret);
|
||||
delete[] entries;
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (s64 i = 0; i < entry_count; i++) {
|
||||
std::string filename = entries[i].name;
|
||||
if (!filename.empty()) {
|
||||
if ((!filename.compare(".")) || (!filename.compare("..")))
|
||||
continue;
|
||||
|
||||
std::string src = src_path;
|
||||
src.append("/");
|
||||
src.append(filename);
|
||||
|
||||
std::string dest = dest_path;
|
||||
dest.append("/");
|
||||
dest.append(filename);
|
||||
|
||||
if (entries[i].type == FsDirEntryType_Dir)
|
||||
FS::CopyDir(src.c_str(), dest.c_str()); // Copy Folder (via recursion)
|
||||
else
|
||||
FS::CopyFile(src.c_str(), dest.c_str()); // Copy File
|
||||
}
|
||||
}
|
||||
|
||||
delete[] entries;
|
||||
fsDirClose(std::addressof(dir));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Copy(FsDirectoryEntry &entry, const std::string &path) {
|
||||
std::string full_path = path;
|
||||
full_path.append("/");
|
||||
full_path.append(entry.name);
|
||||
|
||||
if ((std::strncmp(entry.name, "..", 2)) != 0) {
|
||||
std::strcpy(fs_copy_entry.path, full_path.c_str());
|
||||
std::strcpy(fs_copy_entry.filename, entry.name);
|
||||
|
||||
if (entry.type == FsDirEntryType_Dir)
|
||||
fs_copy_entry.is_directory = true;
|
||||
}
|
||||
}
|
||||
|
||||
Result Paste(void) {
|
||||
Result ret = 0;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
FS::BuildPath(path, fs_copy_entry.filename);
|
||||
|
||||
if (fs_copy_entry.is_directory) // Copy folder recursively
|
||||
ret = FS::CopyDir(fs_copy_entry.path, path);
|
||||
else // Copy file
|
||||
ret = FS::CopyFile(fs_copy_entry.path, path);
|
||||
|
||||
std::memset(fs_copy_entry.path, 0, FS_MAX_PATH);
|
||||
std::memset(fs_copy_entry.filename, 0, FS_MAX_PATH);
|
||||
fs_copy_entry.is_directory = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result Move(void) {
|
||||
Result ret = 0;
|
||||
|
||||
char path[FS_MAX_PATH];
|
||||
FS::BuildPath(path, fs_copy_entry.filename);
|
||||
|
||||
if (fs_copy_entry.is_directory) {
|
||||
if (R_FAILED(ret = fsFsRenameDirectory(fs, fs_copy_entry.path, path))) {
|
||||
Log::Error("fsFsRenameDirectory(%s, %s) failed: 0x%x\n", path, fs_copy_entry.filename, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (R_FAILED(ret = fsFsRenameFile(fs, fs_copy_entry.path, path))) {
|
||||
Log::Error("fsFsRenameFile(%s, %s) failed: 0x%x\n", path, fs_copy_entry.filename, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
std::memset(fs_copy_entry.path, 0, FS_MAX_PATH);
|
||||
std::memset(fs_copy_entry.filename, 0, FS_MAX_PATH);
|
||||
fs_copy_entry.is_directory = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -474,4 +401,30 @@ namespace FS {
|
||||
size = (total_size - free_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string GetFileExt(const std::string &filename) {
|
||||
std::string ext = std::filesystem::path(filename).extension();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::toupper);
|
||||
return ext;
|
||||
}
|
||||
|
||||
std::string BuildPath(FsDirectoryEntry &entry) {
|
||||
std::string path_next = device;
|
||||
path_next.append(cwd);
|
||||
path_next.append((cwd.compare("/") == 0)? "" : "/");
|
||||
path_next.append(entry.name);
|
||||
return path_next;
|
||||
}
|
||||
|
||||
std::string BuildPath(const std::string &path, bool device_name) {
|
||||
std::string path_next = "";
|
||||
|
||||
if (device_name)
|
||||
path_next.append(device);
|
||||
|
||||
path_next.append(cwd);
|
||||
path_next.append((cwd.compare("/") == 0)? "" : "/");
|
||||
path_next.append(path);
|
||||
return path_next;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include <EGL/eglext.h>
|
||||
#include <glad/glad.h>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
#include "gui.hpp"
|
||||
@ -30,8 +31,8 @@ namespace GUI {
|
||||
}
|
||||
|
||||
EGLConfig config;
|
||||
EGLint numConfigs;
|
||||
static const EGLint framebufferAttributeList[] = {
|
||||
EGLint num_configs;
|
||||
static const EGLint framebuffer_attr_list[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
@ -42,8 +43,8 @@ namespace GUI {
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs);
|
||||
if (numConfigs == 0) {
|
||||
eglChooseConfig(s_display, framebuffer_attr_list, std::addressof(config), 1, std::addressof(num_configs));
|
||||
if (num_configs == 0) {
|
||||
Log::Error("No config found! error: %d", eglGetError());
|
||||
eglTerminate(s_display);
|
||||
s_display = nullptr;
|
||||
@ -56,14 +57,14 @@ namespace GUI {
|
||||
s_display = nullptr;
|
||||
}
|
||||
|
||||
static const EGLint contextAttributeList[] = {
|
||||
static const EGLint context_attr_list[] = {
|
||||
EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
|
||||
EGL_CONTEXT_MAJOR_VERSION_KHR, 4,
|
||||
EGL_CONTEXT_MINOR_VERSION_KHR, 3,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
|
||||
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, context_attr_list);
|
||||
if (!s_context) {
|
||||
Log::Error("Context creation failed! error: %d", eglGetError());
|
||||
eglDestroySurface(s_display, s_surface);
|
||||
@ -171,24 +172,24 @@ namespace GUI {
|
||||
PlFontData standard, extended, chinese, korean;
|
||||
static ImWchar extended_range[] = {0xE000, 0xE152};
|
||||
|
||||
if (R_SUCCEEDED(plGetSharedFontByType(&standard, PlSharedFontType_Standard)) &&
|
||||
R_SUCCEEDED(plGetSharedFontByType(&extended, PlSharedFontType_NintendoExt)) &&
|
||||
R_SUCCEEDED(plGetSharedFontByType(&chinese, PlSharedFontType_ChineseSimplified)) &&
|
||||
R_SUCCEEDED(plGetSharedFontByType(&korean, PlSharedFontType_KO))) {
|
||||
if (R_SUCCEEDED(plGetSharedFontByType(std::addressof(standard), PlSharedFontType_Standard)) &&
|
||||
R_SUCCEEDED(plGetSharedFontByType(std::addressof(extended), PlSharedFontType_NintendoExt)) &&
|
||||
R_SUCCEEDED(plGetSharedFontByType(std::addressof(chinese), PlSharedFontType_ChineseSimplified)) &&
|
||||
R_SUCCEEDED(plGetSharedFontByType(std::addressof(korean), PlSharedFontType_KO))) {
|
||||
|
||||
u8 *px = nullptr;
|
||||
int w = 0, h = 0, bpp = 0;
|
||||
ImFontConfig font_cfg;
|
||||
|
||||
font_cfg.FontDataOwnedByAtlas = false;
|
||||
io.Fonts->AddFontFromMemoryTTF(standard.address, standard.size, 20.f, &font_cfg, io.Fonts->GetGlyphRangesDefault());
|
||||
io.Fonts->AddFontFromMemoryTTF(standard.address, standard.size, 20.f, std::addressof(font_cfg), io.Fonts->GetGlyphRangesDefault());
|
||||
font_cfg.MergeMode = true;
|
||||
io.Fonts->AddFontFromMemoryTTF(extended.address, extended.size, 20.f, &font_cfg, extended_range);
|
||||
io.Fonts->AddFontFromMemoryTTF(chinese.address, chinese.size, 20.f, &font_cfg, io.Fonts->GetGlyphRangesChineseFull());
|
||||
io.Fonts->AddFontFromMemoryTTF(korean.address, korean.size, 20.f, &font_cfg, io.Fonts->GetGlyphRangesKorean());
|
||||
io.Fonts->AddFontFromMemoryTTF(extended.address, extended.size, 20.f, std::addressof(font_cfg), extended_range);
|
||||
io.Fonts->AddFontFromMemoryTTF(chinese.address, chinese.size, 20.f, std::addressof(font_cfg), io.Fonts->GetGlyphRangesChineseFull());
|
||||
io.Fonts->AddFontFromMemoryTTF(korean.address, korean.size, 20.f, std::addressof(font_cfg), io.Fonts->GetGlyphRangesKorean());
|
||||
|
||||
// build font atlas
|
||||
io.Fonts->GetTexDataAsAlpha8(&px, &w, &h, &bpp);
|
||||
io.Fonts->GetTexDataAsAlpha8(std::addressof(px), std::addressof(w), std::addressof(h), std::addressof(bpp));
|
||||
io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
|
||||
io.Fonts->Build();
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace ImageViewer {
|
||||
data.selected = index;
|
||||
|
||||
char fs_path[FS_MAX_PATH + 1];
|
||||
if ((std::snprintf(fs_path, FS_MAX_PATH, "%s/%s", cwd, data.entries[index].name)) > 0) {
|
||||
if ((std::snprintf(fs_path, FS_MAX_PATH, "%s/%s", cwd.c_str(), data.entries[index].name)) > 0) {
|
||||
bool ret = Textures::LoadImageFile(fs_path, data.textures);
|
||||
IM_ASSERT(ret);
|
||||
return ret;
|
||||
@ -69,13 +69,13 @@ namespace ImageViewer {
|
||||
if (key & HidNpadButton_X)
|
||||
properties = !properties;
|
||||
|
||||
if (key & HidNpadButton_StickLDown) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_GamepadDpadDown)) {
|
||||
data.zoom_factor -= 0.5f * ImGui::GetIO().DeltaTime;
|
||||
|
||||
if (data.zoom_factor < 0.1f)
|
||||
data.zoom_factor = 0.1f;
|
||||
}
|
||||
else if (key & HidNpadButton_StickLUp) {
|
||||
else if (ImGui::IsKeyDown(ImGuiKey_GamepadDpadUp)) {
|
||||
data.zoom_factor += 0.5f * ImGui::GetIO().DeltaTime;
|
||||
|
||||
if (data.zoom_factor > 5.0f)
|
||||
|
@ -44,14 +44,12 @@ static const char *strings_jp[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -66,6 +64,9 @@ static const char *strings_jp[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -110,14 +111,12 @@ static const char *strings_en[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -132,6 +131,9 @@ static const char *strings_en[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -177,14 +179,12 @@ static const char *strings_fr[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -199,6 +199,9 @@ static const char *strings_fr[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -243,14 +246,12 @@ static const char *strings_de[] {
|
||||
"Einstellungen",
|
||||
"Sortiereinstellung",
|
||||
"Sprache",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Bildanzeige",
|
||||
"Entwickleroptionen",
|
||||
"Über",
|
||||
"Nach Updates suchen",
|
||||
" Nach Name (aufsteigend)",
|
||||
" Nach Name (absteigend)",
|
||||
" Nach Größe (größtes zuerst)",
|
||||
" Nach Größe (kleinstes zuerst)",
|
||||
" Dateiname anzeigen",
|
||||
" Log aktivieren",
|
||||
"Version",
|
||||
@ -265,6 +266,9 @@ static const char *strings_de[] {
|
||||
"Bitte beenden Sie die Anwendung und starten Sie sie erneut.",
|
||||
"Sie sind bereits auf der neusten Version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"Der Name darf nicht leer sein."
|
||||
};
|
||||
|
||||
@ -310,14 +314,12 @@ static const char *strings_it[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -332,6 +334,9 @@ static const char *strings_it[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -377,14 +382,12 @@ static const char *strings_es[] {
|
||||
"Ajustes",
|
||||
"Ajustes de organización",
|
||||
"Idioma",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Visualizador de Imagen",
|
||||
"Opciones de Desarrollador",
|
||||
"Acerca de",
|
||||
"Buscar Actualizaciones",
|
||||
" Por nombre (ascendente)",
|
||||
" Por nombre (descendente)",
|
||||
" Por tamaño (más grande primero)",
|
||||
" Por tamaño (más pequeño primero)",
|
||||
" Mostrar nombre de archivo",
|
||||
" Habilitar logs",
|
||||
"versión",
|
||||
@ -399,6 +402,9 @@ static const char *strings_es[] {
|
||||
"Por favor cerrar y reiniciar la aplicación.",
|
||||
"Estás en la última versión.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"El nombre no puede estar vacío."
|
||||
};
|
||||
|
||||
@ -444,14 +450,12 @@ static const char *strings_sc[] {
|
||||
"设置",
|
||||
"排序方式",
|
||||
"语言",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"图片查看器",
|
||||
"开发人员选项",
|
||||
"关于",
|
||||
"检查更新",
|
||||
" 根据文件名 (升序)",
|
||||
" 根据文件名 (降序)",
|
||||
" 根据大小 (大文件在前)",
|
||||
" 根据大小 (小文件在前)",
|
||||
" 显示文件名",
|
||||
" 打开日志",
|
||||
"版本",
|
||||
@ -466,6 +470,9 @@ static const char *strings_sc[] {
|
||||
"请退出并重新运行应用程序.",
|
||||
"你使用的是最新版本.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"名称不能为空."
|
||||
};
|
||||
|
||||
@ -511,14 +518,12 @@ static const char *strings_ko[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -533,6 +538,9 @@ static const char *strings_ko[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -578,14 +586,12 @@ static const char *strings_nl[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -600,6 +606,9 @@ static const char *strings_nl[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -645,14 +654,12 @@ static const char *strings_pt[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -667,6 +674,9 @@ static const char *strings_pt[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -712,14 +722,12 @@ static const char *strings_ru[] {
|
||||
"Settings",
|
||||
"Sort Settings",
|
||||
"Language",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"Image Viewer",
|
||||
"Developer Options",
|
||||
"About",
|
||||
"Check for Updates",
|
||||
" By name (ascending)",
|
||||
" By name (descending)",
|
||||
" By size (largest first)",
|
||||
" By size (smallest first)",
|
||||
" Display filename",
|
||||
" Enable logs",
|
||||
"version",
|
||||
@ -734,6 +742,9 @@ static const char *strings_ru[] {
|
||||
"Please exit and rerun the application.",
|
||||
"You are on the latest version.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"The name cannot be empty."
|
||||
};
|
||||
|
||||
@ -779,14 +790,12 @@ static const char *strings_tw[] {
|
||||
"設置",
|
||||
"排序方式",
|
||||
"語言",
|
||||
"USB",
|
||||
"Unmount USB devices",
|
||||
"圖片查看器",
|
||||
"開發人員選項",
|
||||
"關於",
|
||||
"檢查更新",
|
||||
" 根據文件名 (升序)",
|
||||
" 根據文件名 (降序)",
|
||||
" 根據大小 (大文件在前)",
|
||||
" 根據大小 (小文件在前)",
|
||||
" 顯示文件名",
|
||||
" 打開日誌",
|
||||
"版本",
|
||||
@ -801,6 +810,9 @@ static const char *strings_tw[] {
|
||||
"請退出並重新運行應用程序.",
|
||||
"妳使用的是最新版本.",
|
||||
|
||||
"Do you wish to unmount all the connected USB devices?",
|
||||
"The USB device can now be safely removed.",
|
||||
|
||||
"名稱不能為空."
|
||||
};
|
||||
|
||||
|
@ -8,13 +8,15 @@ namespace Log {
|
||||
static s64 offset = 0;
|
||||
|
||||
void Init(void) {
|
||||
const char *log_path = "/switch/NX-Shell/debug.log";
|
||||
|
||||
if (!cfg.dev_options)
|
||||
return;
|
||||
|
||||
if (!FS::FileExists("/switch/NX-Shell/debug.log"))
|
||||
fsFsCreateFile(fs, "/switch/NX-Shell/debug.log", 0, 0);
|
||||
if (!FS::FileExists(log_path))
|
||||
fsFsCreateFile(std::addressof(devices[FileSystemSDMC]), log_path, 0, 0);
|
||||
|
||||
if (R_FAILED(fsFsOpenFile(fs, "/switch/NX-Shell/debug.log", (FsOpenMode_Read | FsOpenMode_Write | FsOpenMode_Append), std::addressof(file))))
|
||||
if (R_FAILED(fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), log_path, (FsOpenMode_Read | FsOpenMode_Write | FsOpenMode_Append), std::addressof(file))))
|
||||
return;
|
||||
|
||||
s64 size = 0;
|
||||
|
@ -8,13 +8,14 @@
|
||||
#include "log.hpp"
|
||||
#include "textures.hpp"
|
||||
#include "windows.hpp"
|
||||
#include "usb.hpp"
|
||||
|
||||
char __application_path[FS_MAX_PATH];
|
||||
|
||||
namespace Services {
|
||||
int Init(void) {
|
||||
Result ret = 0;
|
||||
|
||||
|
||||
devices[FileSystemSDMC] = *fsdevGetDeviceFileSystem("sdmc");
|
||||
fs = std::addressof(devices[FileSystemSDMC]);
|
||||
|
||||
@ -47,6 +48,11 @@ namespace Services {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (R_FAILED(ret = USB::Init())) {
|
||||
Log::Error("usbHsFsInitialize(0) failed: 0x%x\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!GUI::Init())
|
||||
Log::Error("GUI::Init() failed: 0x%x\n", ret);
|
||||
|
||||
@ -59,6 +65,7 @@ namespace Services {
|
||||
void Exit(void) {
|
||||
Textures::Exit();
|
||||
GUI::Exit();
|
||||
USB::Exit();
|
||||
nifmExit();
|
||||
socketExit();
|
||||
Log::Exit();
|
||||
@ -70,14 +77,13 @@ namespace Services {
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Result ret = 0;
|
||||
u64 key = 0;
|
||||
|
||||
Services::Init();
|
||||
|
||||
if (R_FAILED(ret = FS::GetDirList(cwd, data.entries))) {
|
||||
if (!FS::GetDirList(device, cwd, data.entries)) {
|
||||
Services::Exit();
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
data.checkbox_data.checked.resize(data.entries.size());
|
||||
|
@ -87,9 +87,9 @@ namespace Net {
|
||||
const char path[FS_MAX_PATH] = "/switch/NX-Shell/NX-Shell_UPDATE.nro";
|
||||
|
||||
if (!FS::FileExists(path))
|
||||
fsFsCreateFile(fs, path, 0, 0);
|
||||
fsFsCreateFile(std::addressof(devices[FileSystemSDMC]), path, 0, 0);
|
||||
|
||||
if (R_FAILED(ret = fsFsOpenFile(fs, path, FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)))) {
|
||||
if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), path, FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)))) {
|
||||
Log::Error("fsFsOpenFile(%s) failed: 0x%x\n", path, ret);
|
||||
return;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace Popups {
|
||||
|
||||
if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::OptionsDelete], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text(strings[cfg.lang][Lang::DeleteMessage]);
|
||||
if ((data.checkbox_data.count > 1) && (strcasecmp(data.checkbox_data.cwd, cwd) == 0)) {
|
||||
if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd == cwd)) {
|
||||
ImGui::Text(strings[cfg.lang][Lang::DeleteMultiplePrompt]);
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::BeginChild("Scrolling", ImVec2(0, 100));
|
||||
@ -32,12 +32,11 @@ namespace Popups {
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::ButtonOK], ImVec2(120, 0))) {
|
||||
Result ret = 0;
|
||||
bool ret = false;
|
||||
|
||||
if ((data.checkbox_data.count > 1) && (strcasecmp(data.checkbox_data.cwd, cwd) == 0)) {
|
||||
if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd == cwd)) {
|
||||
std::vector<FsDirectoryEntry> entries;
|
||||
|
||||
if (R_FAILED(ret = FS::GetDirList(data.checkbox_data.cwd, entries)))
|
||||
if (!FS::GetDirList(data.checkbox_data.device, data.checkbox_data.cwd, entries))
|
||||
return;
|
||||
|
||||
std::sort(entries.begin(), entries.end(), FileBrowser::Sort);
|
||||
@ -48,8 +47,8 @@ namespace Popups {
|
||||
continue;
|
||||
|
||||
if (data.checkbox_data.checked[i]) {
|
||||
if (R_FAILED(ret = FS::Delete(entries[i]))) {
|
||||
FS::GetDirList(cwd, data.entries);
|
||||
if (!(ret = FS::Delete(entries[i]))) {
|
||||
FS::GetDirList(device, cwd, data.entries);
|
||||
Windows::ResetCheckbox(data);
|
||||
break;
|
||||
}
|
||||
@ -61,8 +60,8 @@ namespace Popups {
|
||||
ret = FS::Delete(data.entries[data.selected]);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(ret)) {
|
||||
FS::GetDirList(cwd, data.entries);
|
||||
if (ret) {
|
||||
FS::GetDirList(device, cwd, data.entries);
|
||||
Windows::ResetCheckbox(data);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <glad/glad.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "fs.hpp"
|
||||
@ -13,17 +14,15 @@
|
||||
|
||||
namespace Options {
|
||||
static void RefreshEntries(bool reset_checkbox_data) {
|
||||
FS::GetDirList(cwd, data.entries);
|
||||
FS::GetDirList(device, cwd, data.entries);
|
||||
|
||||
if (reset_checkbox_data)
|
||||
Windows::ResetCheckbox(data);
|
||||
}
|
||||
|
||||
static void HandleMultipleCopy(WindowData &data, Result (*func)()) {
|
||||
Result ret = 0;
|
||||
static void HandleMultipleCopy(WindowData &data, bool (*func)()) {
|
||||
std::vector<FsDirectoryEntry> entries;
|
||||
|
||||
if (R_FAILED(ret = FS::GetDirList(data.checkbox_data.cwd, entries)))
|
||||
if (!FS::GetDirList(data.checkbox_data.device, data.checkbox_data.cwd, entries))
|
||||
return;
|
||||
|
||||
std::sort(entries.begin(), entries.end(), FileBrowser::Sort);
|
||||
@ -33,9 +32,10 @@ namespace Options {
|
||||
continue;
|
||||
|
||||
if (data.checkbox_data.checked_copy[i]) {
|
||||
FS::Copy(entries[i], data.checkbox_data.cwd);
|
||||
std::string path = data.checkbox_data.device + data.checkbox_data.cwd;
|
||||
FS::Copy(entries[i], path);
|
||||
|
||||
if (R_FAILED((*func)())) {
|
||||
if (!(*func)()) {
|
||||
Options::RefreshEntries(true);
|
||||
break;
|
||||
}
|
||||
@ -56,10 +56,11 @@ namespace Popups {
|
||||
|
||||
if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::OptionsTitle], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::OptionsSelectAll], ImVec2(200, 50))) {
|
||||
if ((std::strlen(data.checkbox_data.cwd) != 0) && (strcasecmp(data.checkbox_data.cwd, cwd) != 0))
|
||||
if ((data.checkbox_data.cwd.length() != 0) && (data.checkbox_data.cwd != cwd))
|
||||
Windows::ResetCheckbox(data);
|
||||
|
||||
std::strcpy(data.checkbox_data.cwd, cwd);
|
||||
data.checkbox_data.cwd = cwd;
|
||||
data.checkbox_data.device = device;
|
||||
std::fill(data.checkbox_data.checked.begin() + 1, data.checkbox_data.checked.end(), true);
|
||||
data.checkbox_data.count = data.checkbox_data.checked.size();
|
||||
}
|
||||
@ -96,10 +97,9 @@ namespace Popups {
|
||||
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::OptionsNewFolder], ImVec2(200, 50))) {
|
||||
std::string name = Keyboard::GetText(strings[cfg.lang][Lang::OptionsFolderPrompt], strings[cfg.lang][Lang::OptionsNewFolder]);
|
||||
char path[FS_MAX_PATH];
|
||||
std::snprintf(path, FS_MAX_PATH + 2, "%s/%s", cwd, name.c_str());
|
||||
std::string path = FS::BuildPath(name, true);
|
||||
|
||||
if (R_SUCCEEDED(fsFsCreateDirectory(fs, path))) {
|
||||
if (R_SUCCEEDED(mkdir(path.c_str(), 0700))) {
|
||||
Options::RefreshEntries(true);
|
||||
sort = -1;
|
||||
}
|
||||
@ -112,10 +112,12 @@ namespace Popups {
|
||||
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::OptionsNewFile], ImVec2(200, 50))) {
|
||||
std::string name = Keyboard::GetText(strings[cfg.lang][Lang::OptionsFilePrompt], strings[cfg.lang][Lang::OptionsNewFile]);
|
||||
char path[FS_MAX_PATH];
|
||||
std::snprintf(path, FS_MAX_PATH + 2, "%s/%s", cwd, name.c_str());
|
||||
std::string path = FS::BuildPath(name, true);
|
||||
|
||||
if (R_SUCCEEDED(fsFsCreateFile(fs, path, 0, 0))) {
|
||||
FILE *file = fopen(path.c_str(), "w");
|
||||
fclose(file);
|
||||
|
||||
if (FS::FileExists(path)) {
|
||||
Options::RefreshEntries(true);
|
||||
sort = -1;
|
||||
}
|
||||
@ -128,10 +130,12 @@ namespace Popups {
|
||||
|
||||
if (ImGui::Button(!copy? strings[cfg.lang][Lang::OptionsCopy] : strings[cfg.lang][Lang::OptionsPaste], ImVec2(200, 50))) {
|
||||
if (!copy) {
|
||||
if ((data.checkbox_data.count >= 1) && (strcasecmp(data.checkbox_data.cwd, cwd) != 0))
|
||||
if ((data.checkbox_data.count >= 1) && (data.checkbox_data.cwd != cwd))
|
||||
Windows::ResetCheckbox(data);
|
||||
if (data.checkbox_data.count <= 1)
|
||||
FS::Copy(data.entries[data.selected], cwd);
|
||||
if (data.checkbox_data.count <= 1) {
|
||||
std::string path = device + cwd;
|
||||
FS::Copy(data.entries[data.selected], path);
|
||||
}
|
||||
|
||||
copy = !copy;
|
||||
data.state = WINDOW_STATE_FILEBROWSER;
|
||||
@ -141,10 +145,10 @@ namespace Popups {
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::Render();
|
||||
|
||||
if ((data.checkbox_data.count > 1) && (strcasecmp(data.checkbox_data.cwd, cwd) != 0))
|
||||
if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd != cwd))
|
||||
Options::HandleMultipleCopy(data, std::addressof(FS::Paste));
|
||||
else {
|
||||
if (R_SUCCEEDED(FS::Paste())) {
|
||||
if (FS::Paste()) {
|
||||
Options::RefreshEntries(true);
|
||||
sort = -1;
|
||||
}
|
||||
@ -161,16 +165,18 @@ namespace Popups {
|
||||
|
||||
if (ImGui::Button(!move? strings[cfg.lang][Lang::OptionsMove] : strings[cfg.lang][Lang::OptionsPaste], ImVec2(200, 50))) {
|
||||
if (!move) {
|
||||
if ((data.checkbox_data.count >= 1) && (strcasecmp(data.checkbox_data.cwd, cwd) != 0))
|
||||
if ((data.checkbox_data.count >= 1) && (data.checkbox_data.cwd != cwd))
|
||||
Windows::ResetCheckbox(data);
|
||||
if (data.checkbox_data.count <= 1)
|
||||
FS::Copy(data.entries[data.selected], cwd);
|
||||
if (data.checkbox_data.count <= 1) {
|
||||
std::string path = device + cwd;
|
||||
FS::Copy(data.entries[data.selected], path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((data.checkbox_data.count > 1) && (strcasecmp(data.checkbox_data.cwd, cwd) != 0))
|
||||
if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd != cwd))
|
||||
Options::HandleMultipleCopy(data, std::addressof(FS::Move));
|
||||
else {
|
||||
if (R_SUCCEEDED(FS::Move())) {
|
||||
if (FS::Move()) {
|
||||
Options::RefreshEntries(true);
|
||||
sort = -1;
|
||||
}
|
||||
@ -192,7 +198,9 @@ namespace Popups {
|
||||
ImGui::SameLine(0.0f, 15.0f);
|
||||
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::OptionsSetArchiveBit], ImVec2(200, 50))) {
|
||||
if (R_SUCCEEDED(FS::SetArchiveBit(data.entries[data.selected]))) {
|
||||
std::string path = FS::BuildPath(data.entries[data.selected]);
|
||||
|
||||
if (FS::SetArchiveBit(path)) {
|
||||
Options::RefreshEntries(true);
|
||||
sort = -1;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace Popups {
|
||||
}
|
||||
|
||||
FsTimeStampRaw timestamp;
|
||||
if (R_SUCCEEDED(FS::GetTimeStamp(data.entries[data.selected], timestamp))) {
|
||||
if (FS::GetTimeStamp(data.entries[data.selected], timestamp)) {
|
||||
if (timestamp.is_valid == 1) { // Confirm valid timestamp
|
||||
char date[3][36];
|
||||
|
||||
@ -63,6 +63,7 @@ namespace Popups {
|
||||
std::string new_width, new_height;
|
||||
if (ImGui::BeginPopupModal("Properties", std::addressof(state), ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
std::string parent_text = "Parent: ";
|
||||
parent_text.append(device);
|
||||
parent_text.append(cwd);
|
||||
ImGui::Text(parent_text.c_str());
|
||||
|
||||
|
@ -37,10 +37,10 @@ namespace Popups {
|
||||
Net::GetLatestReleaseNRO(tag);
|
||||
|
||||
Result ret = 0;
|
||||
if (R_FAILED(ret = fsFsDeleteFile(fs, __application_path)))
|
||||
if (R_FAILED(ret = fsFsDeleteFile(std::addressof(devices[FileSystemSDMC]), __application_path)))
|
||||
Log::Error("fsFsDeleteFile(%s) failed: 0x%x\n", __application_path, ret);
|
||||
|
||||
if (R_FAILED(ret = fsFsRenameFile(fs, "/switch/NX-Shell/NX-Shell_UPDATE.nro", __application_path)))
|
||||
if (R_FAILED(ret = fsFsRenameFile(std::addressof(devices[FileSystemSDMC]), "/switch/NX-Shell/NX-Shell_UPDATE.nro", __application_path)))
|
||||
Log::Error("fsFsRenameFile(update) failed: 0x%x\n", ret);
|
||||
|
||||
done = true;
|
||||
|
46
source/popups/usbprompt.cpp
Normal file
46
source/popups/usbprompt.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "config.hpp"
|
||||
#include "imgui.h"
|
||||
#include "language.hpp"
|
||||
#include "popups.hpp"
|
||||
#include "usb.hpp"
|
||||
|
||||
namespace Popups {
|
||||
static bool done = false;
|
||||
|
||||
void USBPopup(bool &state) {
|
||||
Popups::SetupPopup(strings[cfg.lang][Lang::SettingsUSBTitle]);
|
||||
|
||||
if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::SettingsUSBTitle], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
if (!done)
|
||||
ImGui::Text(strings[cfg.lang][Lang::USBUnmountPrompt]);
|
||||
else
|
||||
ImGui::Text(strings[cfg.lang][Lang::USBUnmountSuccess]);
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::ButtonOK], ImVec2(120, 0))) {
|
||||
if (!done) {
|
||||
USB::Unmount();
|
||||
ImGui::CloseCurrentPopup();
|
||||
done = true;
|
||||
}
|
||||
else {
|
||||
ImGui::CloseCurrentPopup();
|
||||
done = false;
|
||||
state = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine(0.0f, 15.0f);
|
||||
|
||||
if (!done) {
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::ButtonCancel], ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
state = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popups::ExitPopup();
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
#include "utils.hpp"
|
||||
|
||||
int sort = 0;
|
||||
std::vector<std::string> devices_list = { "sdmc:", "safe:", "user:", "system:" };
|
||||
|
||||
namespace FileBrowser {
|
||||
// Sort without using ImGuiTableSortSpecs
|
||||
@ -46,16 +47,9 @@ namespace FileBrowser {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tabs {
|
||||
static const u32 sampler_id = 1;
|
||||
static const ImVec2 tex_size = ImVec2(25, 25);
|
||||
static std::vector<std::string> devices_list = { "sdmc:", "safe:", "user:", "system:" };
|
||||
static std::string device = "sdmc:";
|
||||
|
||||
// Sort using ImGuiTableSortSpecs
|
||||
bool Sort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB) {
|
||||
bool TableSort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB) {
|
||||
bool descending = false;
|
||||
ImGuiTableSortSpecs *table_sort_specs = ImGui::TableGetSortSpecs();
|
||||
|
||||
@ -94,6 +88,10 @@ namespace Tabs {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tabs {
|
||||
static const ImVec2 tex_size = ImVec2(21, 21);
|
||||
|
||||
void FileBrowser(WindowData &data) {
|
||||
if (ImGui::BeginTabItem("File Browser")) {
|
||||
@ -108,9 +106,14 @@ namespace Tabs {
|
||||
if (ImGui::Selectable(devices_list[i].c_str(), is_selected)) {
|
||||
device = devices_list[i];
|
||||
fs = std::addressof(devices[i]);
|
||||
std::strncpy(cwd, "/", 2);
|
||||
|
||||
cwd = "/";
|
||||
data.entries.clear();
|
||||
FS::GetDirList(cwd, data.entries);
|
||||
FS::GetDirList(device, cwd, data.entries);
|
||||
|
||||
data.checkbox_data.checked.resize(data.entries.size());
|
||||
FS::GetUsedStorageSpace(data.used_storage);
|
||||
FS::GetTotalStorageSpace(data.total_storage);
|
||||
sort = -1;
|
||||
}
|
||||
|
||||
@ -126,7 +129,7 @@ namespace Tabs {
|
||||
ImGui::SameLine();
|
||||
|
||||
// Display current working directory
|
||||
ImGui::Text(cwd);
|
||||
ImGui::Text(cwd.c_str());
|
||||
|
||||
// Draw storage bar
|
||||
ImGui::Dummy(ImVec2(0.0f, 1.0f)); // Spacing
|
||||
@ -150,7 +153,7 @@ namespace Tabs {
|
||||
sorts_specs->SpecsDirty = true;
|
||||
|
||||
if (sorts_specs->SpecsDirty) {
|
||||
std::sort(data.entries.begin(), data.entries.end(), Tabs::Sort);
|
||||
std::sort(data.entries.begin(), data.entries.end(), FileBrowser::TableSort);
|
||||
sorts_specs->SpecsDirty = false;
|
||||
}
|
||||
}
|
||||
@ -161,7 +164,7 @@ namespace Tabs {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(i);
|
||||
|
||||
if ((data.checkbox_data.checked[i]) && (strcasecmp(data.checkbox_data.cwd, cwd) == 0))
|
||||
if ((data.checkbox_data.checked[i]) && (data.checkbox_data.cwd.compare(cwd) == 0) && (data.checkbox_data.device.compare(device) == 0))
|
||||
ImGui::Image(reinterpret_cast<ImTextureID>(check_icon.id), tex_size);
|
||||
else
|
||||
ImGui::Image(reinterpret_cast<ImTextureID>(uncheck_icon.id), tex_size);
|
||||
@ -181,22 +184,19 @@ namespace Tabs {
|
||||
if (ImGui::Selectable(data.entries[i].name, false)) {
|
||||
if (data.entries[i].type == FsDirEntryType_Dir) {
|
||||
if (std::strncmp(data.entries[i].name, "..", 2) == 0) {
|
||||
if (R_SUCCEEDED(FS::ChangeDirPrev(data.entries))) {
|
||||
if (FS::ChangeDirPrev(data.entries)) {
|
||||
if ((data.checkbox_data.count > 1) && (data.checkbox_data.checked_copy.empty()))
|
||||
data.checkbox_data.checked_copy = data.checkbox_data.checked;
|
||||
|
||||
data.checkbox_data.checked.resize(data.entries.size());
|
||||
}
|
||||
}
|
||||
else if ((FS::ChangeDirNext(data.entries[i].name, data.entries)) >= 0) {
|
||||
else if (FS::ChangeDirNext(data.entries[i].name, data.entries)) {
|
||||
if ((data.checkbox_data.count > 1) && (data.checkbox_data.checked_copy.empty()))
|
||||
data.checkbox_data.checked_copy = data.checkbox_data.checked;
|
||||
|
||||
data.checkbox_data.checked.resize(data.entries.size());
|
||||
}
|
||||
else {
|
||||
std::printf("ChangeDirNext failed?\n");
|
||||
}
|
||||
|
||||
// Reset navigation ID -- TODO: Scroll to top
|
||||
ImGuiContext& g = *GImGui;
|
||||
@ -207,14 +207,12 @@ namespace Tabs {
|
||||
sorts_specs->SpecsDirty = true;
|
||||
}
|
||||
else {
|
||||
char path[FS_MAX_PATH + 1];
|
||||
|
||||
std::string path = FS::BuildPath(data.entries[i]);
|
||||
|
||||
switch (file_type) {
|
||||
case FileTypeImage:
|
||||
if ((std::snprintf(path, FS_MAX_PATH, "%s/%s", cwd, data.entries[i].name)) > 0) {
|
||||
Textures::LoadImageFile(path, data.textures);
|
||||
if (Textures::LoadImageFile(path, data.textures))
|
||||
data.state = WINDOW_STATE_IMAGEVIEWER;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -2,19 +2,29 @@
|
||||
#include "fs.hpp"
|
||||
#include "gui.hpp"
|
||||
#include "imgui.h"
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui_internal.h"
|
||||
#include "language.hpp"
|
||||
#include "net.hpp"
|
||||
#include "popups.hpp"
|
||||
#include "tabs.hpp"
|
||||
#include "usb.hpp"
|
||||
|
||||
namespace Tabs {
|
||||
static bool update_popup = false, network_status = false, update_available = false;
|
||||
static bool update_popup = false, network_status = false, update_available = false, unmount_popup = false;
|
||||
static std::string tag_name = std::string();
|
||||
|
||||
void Separator(void) {
|
||||
static void Indent(const std::string &title) {
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_CheckMark], title.c_str());
|
||||
ImGui::Indent(20.f);
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
}
|
||||
|
||||
static void Separator(void) {
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::Unindent();
|
||||
ImGui::Separator();
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
}
|
||||
|
||||
void Settings(WindowData &data) {
|
||||
@ -52,42 +62,47 @@ namespace Tabs {
|
||||
|
||||
// ImGui::Separator();
|
||||
|
||||
// Image filename checkbox
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
// USB unmount
|
||||
ImGui::Indent(10.f);
|
||||
ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_CheckMark], strings[cfg.lang][Lang::SettingsImageViewTitle]);
|
||||
ImGui::Indent(20.f);
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
Tabs::Indent(strings[cfg.lang][Lang::SettingsUSBTitle]);
|
||||
|
||||
if (!USB::Connected()) {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
|
||||
}
|
||||
|
||||
if (ImGui::Button(strings[cfg.lang][Lang::SettingsUSBUnmount], ImVec2(250, 50)))
|
||||
unmount_popup = true;
|
||||
|
||||
if (!USB::Connected()) {
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
Tabs::Separator();
|
||||
|
||||
// Image filename checkbox
|
||||
Tabs::Indent(strings[cfg.lang][Lang::SettingsImageViewTitle]);
|
||||
|
||||
if (ImGui::Checkbox(strings[cfg.lang][Lang::SettingsImageViewFilenameToggle], std::addressof(cfg.image_filename)))
|
||||
Config::Save(cfg);
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::Unindent();
|
||||
ImGui::Separator();
|
||||
Tabs::Separator();
|
||||
|
||||
// Developer Options Checkbox
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_CheckMark], strings[cfg.lang][Lang::SettingsDevOptsTitle]);
|
||||
ImGui::Indent(20.f);
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
Tabs::Indent(strings[cfg.lang][Lang::SettingsDevOptsTitle]);
|
||||
|
||||
if (ImGui::Checkbox(strings[cfg.lang][Lang::SettingsDevOptsLogsToggle], std::addressof(cfg.dev_options)))
|
||||
Config::Save(cfg);
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::Unindent();
|
||||
ImGui::Separator();
|
||||
Tabs::Separator();
|
||||
|
||||
// About
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_CheckMark], strings[cfg.lang][Lang::SettingsAboutTitle]);
|
||||
ImGui::Indent(20.f);
|
||||
Tabs::Indent(strings[cfg.lang][Lang::SettingsAboutTitle]);
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::Text("NX-Shell %s: v%d.%d.%d", strings[cfg.lang][Lang::SettingsAboutVersion], VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::Text("ImGui %s: %s", strings[cfg.lang][Lang::SettingsAboutVersion], ImGui::GetVersion());
|
||||
ImGui::Text("Dear ImGui %s: %s", strings[cfg.lang][Lang::SettingsAboutVersion], ImGui::GetVersion());
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
ImGui::Text("%s: Joel16", strings[cfg.lang][Lang::SettingsAboutAuthor]);
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
|
||||
@ -106,5 +121,8 @@ namespace Tabs {
|
||||
|
||||
if (update_popup)
|
||||
Popups::UpdatePopup(update_popup, network_status, update_available, tag_name);
|
||||
|
||||
if (unmount_popup)
|
||||
Popups::USBPopup(unmount_popup);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// BMP
|
||||
#include "libnsbmp.h"
|
||||
@ -79,38 +80,31 @@ namespace Textures {
|
||||
ImageTypeOther
|
||||
} ImageType;
|
||||
|
||||
static Result ReadFile(const char path[FS_MAX_PATH], unsigned char **buffer, s64 &size) {
|
||||
Result ret = 0;
|
||||
FsFile file;
|
||||
|
||||
if (R_FAILED(ret = fsFsOpenFile(fs, path, FsOpenMode_Read, std::addressof(file)))) {
|
||||
Log::Error("fsFsOpenFile(%s) failed: 0x%x\n", path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (R_FAILED(ret = fsFileGetSize(std::addressof(file), std::addressof(size)))) {
|
||||
Log::Error("fsFileGetSize(%s) failed: 0x%x\n", path, ret);
|
||||
fsFileClose(std::addressof(file));
|
||||
return ret;
|
||||
static bool ReadFile(const std::string &path, unsigned char **buffer, std::size_t &size) {
|
||||
FILE *file = fopen(path.c_str(), "rb");
|
||||
if (!file) {
|
||||
Log::Error("Textures::ReadFile (%s) failed to open file.\n", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
*buffer = new unsigned char[size];
|
||||
|
||||
u64 bytes_read = 0;
|
||||
if (R_FAILED(ret = fsFileRead(std::addressof(file), 0, *buffer, static_cast<u64>(size), FsReadOption_None, std::addressof(bytes_read)))) {
|
||||
Log::Error("fsFileRead(%s) failed: 0x%x\n", path, ret);
|
||||
fsFileClose(std::addressof(file));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bytes_read != static_cast<u64>(size)) {
|
||||
Log::Error("bytes_read(%llu) does not match file size(%llu)\n", bytes_read, size);
|
||||
fsFileClose(std::addressof(file));
|
||||
return -1;
|
||||
struct stat file_stat = { 0 };
|
||||
if (stat(path.c_str(), std::addressof(file_stat)) != 0) {
|
||||
Log::Error("Textures::ReadFile (%s) failed to get file size.\n", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fsFileClose(std::addressof(file));
|
||||
return 0;
|
||||
size = file_stat.st_size;
|
||||
*buffer = new unsigned char[size + 1];
|
||||
std::size_t bytes_read = fread(*buffer, sizeof(unsigned char), size, file);
|
||||
|
||||
if (bytes_read != size) {
|
||||
Log::Error("Textures::ReadFile (%s) failed to read file.\n", path.c_str());
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Create(unsigned char *data, GLint format, Tex &texture) {
|
||||
@ -130,7 +124,7 @@ namespace Textures {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadImageRomfs(const std::string &path, Tex &texture) {
|
||||
static bool LoadImagePNG(const std::string &path, Tex &texture) {
|
||||
bool ret = false;
|
||||
png_image image;
|
||||
std::memset(std::addressof(image), 0, (sizeof image));
|
||||
@ -159,7 +153,7 @@ namespace Textures {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool LoadImageBMP(unsigned char **data, s64 &size, Tex &texture) {
|
||||
static bool LoadImageBMP(unsigned char **data, std::size_t &size, Tex &texture) {
|
||||
bmp_bitmap_callback_vt bitmap_callbacks = {
|
||||
BMP::bitmap_create,
|
||||
BMP::bitmap_destroy,
|
||||
@ -295,54 +289,25 @@ namespace Textures {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadImageJPEG(unsigned char **data, s64 &size, Tex &texture) {
|
||||
static bool LoadImageJPEG(unsigned char **data, std::size_t &size, Tex &texture) {
|
||||
tjhandle jpeg = tjInitDecompress();
|
||||
int jpegsubsamp = 0;
|
||||
tjDecompressHeader2(jpeg, *data, size, std::addressof(texture.width), std::addressof(texture.height), std::addressof(jpegsubsamp));
|
||||
unsigned char *buffer = new unsigned char[texture.width * texture.height * 3];
|
||||
tjDecompress2(jpeg, *data, size, buffer, texture.width, 0, texture.height, TJPF_RGB, TJFLAG_FASTDCT);
|
||||
bool ret = Textures::Create(buffer, GL_RGB, texture);
|
||||
unsigned char *buffer = new unsigned char[texture.width * texture.height * 4];
|
||||
tjDecompress2(jpeg, *data, size, buffer, texture.width, 0, texture.height, TJPF_RGBA, TJFLAG_FASTDCT);
|
||||
bool ret = Textures::Create(buffer, GL_RGBA, texture);
|
||||
tjDestroy(jpeg);
|
||||
delete[] buffer;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool LoadImageOther(unsigned char **data, s64 &size, Tex &texture) {
|
||||
unsigned char *image = stbi_load_from_memory(*data, size, std::addressof(texture.width), std::addressof(texture.height), nullptr, STBI_rgb_alpha);
|
||||
static bool LoadImageOther(const std::string &path, Tex &texture) {
|
||||
unsigned char *image = stbi_load(path.c_str(), std::addressof(texture.width), std::addressof(texture.height), nullptr, STBI_rgb_alpha);
|
||||
bool ret = Textures::Create(image, GL_RGBA, texture);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool LoadImagePNG(unsigned char **data, s64 &size, Tex &texture) {
|
||||
bool ret = false;
|
||||
png_image image;
|
||||
std::memset(std::addressof(image), 0, (sizeof image));
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
|
||||
if (png_image_begin_read_from_memory(std::addressof(image), *data, size) != 0) {
|
||||
png_bytep buffer;
|
||||
image.format = PNG_FORMAT_RGBA;
|
||||
buffer = new png_byte[PNG_IMAGE_SIZE(image)];
|
||||
|
||||
if (buffer != nullptr && png_image_finish_read(std::addressof(image), nullptr, buffer, 0, nullptr) != 0) {
|
||||
texture.width = image.width;
|
||||
texture.height = image.height;
|
||||
ret = Textures::Create(buffer, GL_RGBA, texture);
|
||||
delete[] buffer;
|
||||
png_image_free(std::addressof(image));
|
||||
}
|
||||
else {
|
||||
if (buffer == nullptr)
|
||||
png_image_free(std::addressof(image));
|
||||
else
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool LoadImageWEBP(unsigned char **data, s64 &size, Tex &texture) {
|
||||
static bool LoadImageWEBP(unsigned char **data, std::size_t &size, Tex &texture) {
|
||||
*data = WebPDecodeRGBA(*data, size, std::addressof(texture.width), std::addressof(texture.height));
|
||||
bool ret = Textures::Create(*data, GL_RGBA, texture);
|
||||
return ret;
|
||||
@ -365,7 +330,7 @@ namespace Textures {
|
||||
return ImageTypeOther;
|
||||
}
|
||||
|
||||
bool LoadImageFile(const char path[FS_MAX_PATH], std::vector<Tex> &textures) {
|
||||
bool LoadImageFile(const std::string &path, std::vector<Tex> &textures) {
|
||||
bool ret = false;
|
||||
|
||||
// Resize to 1 initially. If the file is a GIF it will be resized accordingly.
|
||||
@ -375,11 +340,15 @@ namespace Textures {
|
||||
|
||||
if (type == ImageTypeGIF)
|
||||
ret = Textures::LoadImageGIF(path, textures);
|
||||
else if (type == ImageTypePNG)
|
||||
ret = Textures::LoadImagePNG(path, textures[0]);
|
||||
else if (type == ImageTypeOther)
|
||||
ret = Textures::LoadImageOther(path, textures[0]);
|
||||
else {
|
||||
unsigned char *data = nullptr;
|
||||
s64 size = 0;
|
||||
std::size_t size = 0;
|
||||
|
||||
if (R_FAILED(Textures::ReadFile(path, std::addressof(data), size))) {
|
||||
if (!Textures::ReadFile(path, std::addressof(data), size)) {
|
||||
delete[] data;
|
||||
return ret;
|
||||
}
|
||||
@ -393,16 +362,11 @@ namespace Textures {
|
||||
ret = Textures::LoadImageJPEG(std::addressof(data), size, textures[0]);
|
||||
break;
|
||||
|
||||
case ImageTypePNG:
|
||||
ret = Textures::LoadImagePNG(std::addressof(data), size, textures[0]);
|
||||
break;
|
||||
|
||||
case ImageTypeWEBP:
|
||||
ret = Textures::LoadImageWEBP(std::addressof(data), size, textures[0]);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = Textures::LoadImageOther(std::addressof(data), size, textures[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -421,19 +385,19 @@ namespace Textures {
|
||||
"romfs:/text.png"
|
||||
};
|
||||
|
||||
bool image_ret = Textures::LoadImageRomfs("romfs:/folder.png", folder_icon);
|
||||
bool image_ret = Textures::LoadImagePNG("romfs:/folder.png", folder_icon);
|
||||
IM_ASSERT(image_ret);
|
||||
|
||||
image_ret = Textures::LoadImageRomfs("romfs:/check.png", check_icon);
|
||||
image_ret = Textures::LoadImagePNG("romfs:/check.png", check_icon);
|
||||
IM_ASSERT(image_ret);
|
||||
|
||||
image_ret = Textures::LoadImageRomfs("romfs:/uncheck.png", uncheck_icon);
|
||||
image_ret = Textures::LoadImagePNG("romfs:/uncheck.png", uncheck_icon);
|
||||
IM_ASSERT(image_ret);
|
||||
|
||||
file_icons.resize(num_icons);
|
||||
|
||||
for (int i = 0; i < num_icons; i++) {
|
||||
bool ret = Textures::LoadImageRomfs(paths[i], file_icons[i]);
|
||||
bool ret = Textures::LoadImagePNG(paths[i], file_icons[i]);
|
||||
IM_ASSERT(ret);
|
||||
}
|
||||
}
|
||||
|
113
source/usb.cpp
Normal file
113
source/usb.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
#include <cstdio>
|
||||
|
||||
#include "usb.hpp"
|
||||
#include "usbhsfs.h"
|
||||
#include "windows.hpp"
|
||||
|
||||
namespace USB {
|
||||
static UEvent *status_change_event = nullptr, exit_event = {0};
|
||||
static u32 usb_device_count = 0;
|
||||
static UsbHsFsDevice *usb_devices = nullptr;
|
||||
static Thread thread;
|
||||
static u32 listed_device_count = 0;
|
||||
|
||||
// This function is heavily based off the example provided by DarkMatterCore
|
||||
// https://github.com/DarkMatterCore/libusbhsfs/blob/main/example/source/main.c
|
||||
static void usbMscThreadFunc(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
Result ret = 0;
|
||||
int idx = 0;
|
||||
|
||||
/* Generate waiters for our user events. */
|
||||
Waiter status_change_event_waiter = waiterForUEvent(status_change_event);
|
||||
Waiter exit_event_waiter = waiterForUEvent(&exit_event);
|
||||
|
||||
while(true) {
|
||||
/* Wait until an event is triggered. */
|
||||
if (R_FAILED(ret = waitMulti(&idx, -1, status_change_event_waiter, exit_event_waiter)))
|
||||
continue;
|
||||
|
||||
/* Exit event triggered. */
|
||||
if (idx == 1)
|
||||
break;
|
||||
|
||||
/* Get mounted device count. */
|
||||
usb_device_count = usbHsFsGetMountedDeviceCount();
|
||||
|
||||
if (!usb_device_count) {
|
||||
USB::Unmount();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Free mounted devices buffer. */
|
||||
if (usb_devices)
|
||||
delete[] usb_devices;
|
||||
|
||||
/* Allocate mounted devices buffer. */
|
||||
usb_devices = new UsbHsFsDevice[usb_device_count];
|
||||
if (!usb_devices)
|
||||
continue;
|
||||
|
||||
/* List mounted devices. */
|
||||
if (!(listed_device_count = usbHsFsListMountedDevices(usb_devices, usb_device_count)))
|
||||
continue;
|
||||
|
||||
/* Print info from mounted devices. */
|
||||
for(u32 i = 0; i < listed_device_count; i++) {
|
||||
UsbHsFsDevice *device = std::addressof(usb_devices[i]);
|
||||
devices_list.push_back(device->name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Exit thread. */
|
||||
return;
|
||||
}
|
||||
|
||||
Result Init(void) {
|
||||
Result ret = usbHsFsInitialize(0);
|
||||
|
||||
/* Get USB Mass Storage status change event. */
|
||||
status_change_event = usbHsFsGetStatusChangeUserEvent();
|
||||
|
||||
/* Create usermode thread exit event. */
|
||||
ueventCreate(&exit_event, true);
|
||||
|
||||
/* Create thread. */
|
||||
if (R_SUCCEEDED(ret = threadCreate(&thread, usbMscThreadFunc, nullptr, nullptr, 0x10000, 0x2C, -2)))
|
||||
ret = threadStart(&thread);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Exit(void) {
|
||||
/* Signal background thread. */
|
||||
ueventSignal(&exit_event);
|
||||
|
||||
/* Wait for the background thread to exit on its own. */
|
||||
threadWaitForExit(&thread);
|
||||
threadClose(&thread);
|
||||
|
||||
/* Clean up and exit. */
|
||||
USB::Unmount();
|
||||
usbHsFsExit();
|
||||
}
|
||||
|
||||
bool Connected(void) {
|
||||
return (listed_device_count > 0);
|
||||
}
|
||||
|
||||
void Unmount(void) {
|
||||
/* Unmount devices. */
|
||||
for(u32 i = 0; i < listed_device_count; i++) {
|
||||
UsbHsFsDevice *device = std::addressof(usb_devices[i]);
|
||||
devices_list.pop_back();
|
||||
usbHsFsUnmountDevice(device, false);
|
||||
}
|
||||
|
||||
listed_device_count = 0;
|
||||
|
||||
if (usb_devices)
|
||||
delete[] usb_devices;
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace Windows {
|
||||
data.checkbox_data.checked_copy.clear();
|
||||
data.checkbox_data.checked.resize(data.entries.size());
|
||||
data.checkbox_data.checked.assign(data.checkbox_data.checked.size(), false);
|
||||
data.checkbox_data.cwd[0] = '\0';
|
||||
data.checkbox_data.cwd = "";
|
||||
data.checkbox_data.count = 0;
|
||||
};
|
||||
|
||||
@ -72,11 +72,12 @@ namespace Windows {
|
||||
data.state = WINDOW_STATE_OPTIONS;
|
||||
|
||||
if (key & HidNpadButton_Y) {
|
||||
if ((std::strlen(data.checkbox_data.cwd) != 0) && (strcasecmp(data.checkbox_data.cwd, cwd) != 0))
|
||||
if ((data.checkbox_data.cwd.length() != 0) && (data.checkbox_data.cwd != cwd))
|
||||
Windows::ResetCheckbox(data);
|
||||
|
||||
if ((std::strncmp(data.entries[data.selected].name, "..", 2)) != 0) {
|
||||
std::strcpy(data.checkbox_data.cwd, cwd);
|
||||
data.checkbox_data.cwd = cwd;
|
||||
data.checkbox_data.device = device;
|
||||
data.checkbox_data.checked.at(data.selected) = !data.checkbox_data.checked.at(data.selected);
|
||||
data.checkbox_data.count = std::count(data.checkbox_data.checked.begin(), data.checkbox_data.checked.end(), true);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user