fs: Refactor FS to use libc and add support for browsing USB devices via libusbhsfs

This commit is contained in:
Joel16 2022-08-06 11:33:30 -04:00
parent 6f3d028927
commit 1107e604b3
30 changed files with 939 additions and 666 deletions

3
.gitignore vendored
View File

@ -36,4 +36,5 @@
*.elf
*.nacp
build/
res/shaders
res/shaders
!libs/lib/libusbhsfs.a

View File

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

View File

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

View File

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

View File

@ -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 &timestamp);
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 &timestamp);
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);
}

View File

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

View File

@ -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);
}

View File

@ -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
View 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);
}

View File

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

Binary file not shown.

126
libs/usbhsfs.h Normal file
View 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__ */

View File

@ -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);
}

View File

@ -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 &timestamp) {
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 &timestamp) {
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;
}
}

View File

@ -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();
}

View File

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

View File

@ -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.",
"名稱不能為空."
};

View File

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

View File

@ -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());

View File

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

View File

@ -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);
}

View File

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

View File

@ -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());

View File

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

View 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();
}
}

View File

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

View File

@ -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);
}
}

View File

@ -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
View 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;
}
}

View File

@ -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);
}