add mtp install, fix es ticket being the wrong size, fix yati not returning the read fail result, updated haze, updated translations

see #132
This commit is contained in:
ITotalJustice
2025-06-17 10:48:07 +01:00
parent 4ef15f8b81
commit 1c72350d4a
29 changed files with 676 additions and 798 deletions

View File

@@ -4,7 +4,6 @@
#include "fs.hpp"
#include "log.hpp"
#include <mutex>
#include <algorithm>
#include <minIni.h>
#include <ftpsrv.h>
@@ -16,8 +15,7 @@ namespace sphaira::ftpsrv {
namespace {
struct InstallSharedData {
std::mutex mutex;
Mutex mutex;
std::deque<std::string> queued_files;
void* user;
@@ -36,7 +34,7 @@ FtpSrvConfig g_ftpsrv_config = {0};
volatile bool g_should_exit = false;
bool g_is_running{false};
Thread g_thread;
std::mutex g_mutex{};
Mutex g_mutex{};
InstallSharedData g_shared_data{};
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
@@ -59,13 +57,13 @@ struct VfsUserData {
// ive given up with good names.
void on_thing() {
log_write("[FTP] doing on_thing\n");
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
log_write("[FTP] locked on_thing\n");
if (!g_shared_data.in_progress) {
if (!g_shared_data.queued_files.empty()) {
log_write("[FTP] pushing new file data\n");
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.user, g_shared_data.queued_files[0].c_str())) {
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.queued_files[0].c_str())) {
g_shared_data.queued_files.clear();
} else {
log_write("[FTP] success on new file push\n");
@@ -77,7 +75,7 @@ void on_thing() {
int vfs_install_open(void* user, const char* path, enum FtpVfsOpenMode mode) {
{
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
auto data = static_cast<VfsUserData*>(user);
data->valid = 0;
@@ -133,7 +131,7 @@ int vfs_install_read(void* user, void* buf, size_t size) {
}
int vfs_install_write(void* user, const void* buf, size_t size) {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
if (!g_shared_data.enabled) {
errno = EACCES;
return -1;
@@ -145,7 +143,7 @@ int vfs_install_write(void* user, const void* buf, size_t size) {
return -1;
}
if (!g_shared_data.on_write || !g_shared_data.on_write(g_shared_data.user, buf, size)) {
if (!g_shared_data.on_write || !g_shared_data.on_write(buf, size)) {
errno = EIO;
return -1;
}
@@ -159,13 +157,13 @@ int vfs_install_seek(void* user, const void* buf, size_t size, size_t off) {
}
int vfs_install_isfile_open(void* user) {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
auto data = static_cast<VfsUserData*>(user);
return data->valid;
}
int vfs_install_isfile_ready(void* user) {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
auto data = static_cast<VfsUserData*>(user);
const auto ready = !g_shared_data.queued_files.empty() && data->path == g_shared_data.queued_files[0];
return ready;
@@ -174,7 +172,7 @@ int vfs_install_isfile_ready(void* user) {
int vfs_install_close(void* user) {
{
log_write("[FTP] closing file\n");
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
auto data = static_cast<VfsUserData*>(user);
if (data->valid) {
log_write("[FTP] closing valid file\n");
@@ -184,7 +182,7 @@ int vfs_install_close(void* user) {
if (it == g_shared_data.queued_files.cbegin()) {
log_write("[FTP] closing current file\n");
if (g_shared_data.on_close) {
g_shared_data.on_close(g_shared_data.user);
g_shared_data.on_close();
}
g_shared_data.in_progress = false;
@@ -296,7 +294,7 @@ void loop(void* arg) {
} // namespace
bool Init() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
if (g_is_running) {
log_write("[FTP] already enabled, cannot open\n");
return false;
@@ -380,7 +378,7 @@ bool Init() {
}
void Exit() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
if (!g_is_running) {
return;
}
@@ -398,9 +396,8 @@ void Exit() {
log_write("[FTP] exitied\n");
}
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
std::scoped_lock lock{g_shared_data.mutex};
g_shared_data.user = user;
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
SCOPED_MUTEX(&g_shared_data.mutex);
g_shared_data.on_start = on_start;
g_shared_data.on_write = on_write;
g_shared_data.on_close = on_close;
@@ -408,27 +405,27 @@ void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_writ
}
void DisableInstallMode() {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
g_shared_data.enabled = false;
}
unsigned GetPort() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
return g_ftpsrv_config.port;
}
bool IsAnon() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
return g_ftpsrv_config.anon;
}
const char* GetUser() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
return g_ftpsrv_config.user;
}
const char* GetPass() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
return g_ftpsrv_config.pass;
}

View File

@@ -6,7 +6,6 @@
#include "evman.hpp"
#include "i18n.hpp"
#include <mutex>
#include <algorithm>
#include <haze.h>
@@ -14,8 +13,8 @@ namespace sphaira::haze {
namespace {
struct InstallSharedData {
std::mutex mutex;
std::deque<std::string> queued_files;
Mutex mutex;
std::string current_file;
void* user;
OnInstallStart on_start;
@@ -30,7 +29,7 @@ constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 2;
volatile bool g_should_exit = false;
bool g_is_running{false};
std::mutex g_mutex{};
Mutex g_mutex{};
InstallSharedData g_shared_data{};
const char* SUPPORTED_EXT[] = {
@@ -40,14 +39,14 @@ const char* SUPPORTED_EXT[] = {
// ive given up with good names.
void on_thing() {
log_write("[MTP] doing on_thing\n");
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
log_write("[MTP] locked on_thing\n");
if (!g_shared_data.in_progress) {
if (!g_shared_data.queued_files.empty()) {
if (!g_shared_data.current_file.empty()) {
log_write("[MTP] pushing new file data\n");
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.user, g_shared_data.queued_files[0].c_str())) {
g_shared_data.queued_files.clear();
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.current_file.c_str())) {
g_shared_data.current_file.clear();
} else {
log_write("[MTP] success on new file push\n");
g_shared_data.in_progress = true;
@@ -71,7 +70,7 @@ struct FsProxyBase : ::haze::FileSystemProxyImpl {
std::strcpy(buf, path);
}
log_write("[FixPath] %s -> %s\n", path, buf.s);
// log_write("[FixPath] %s -> %s\n", path, buf.s);
return buf;
}
@@ -233,8 +232,162 @@ private:
std::shared_ptr<fs::Fs> m_fs{};
};
struct FsDevNullProxy final : FsProxyBase {
// fake fs that allows for files to create r/w on the root.
// folders are not yet supported.
struct FsProxyVfs : FsProxyBase {
using FsProxyBase::FsProxyBase;
virtual ~FsProxyVfs() = default;
auto GetFileName(const char* s) -> const char* {
const auto file_name = std::strrchr(s, '/');
if (!file_name || file_name[1] == '\0') {
return nullptr;
}
return file_name + 1;
}
virtual Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) {
if (FixPath(path) == "/") {
*out_entry_type = FsDirEntryType_Dir;
R_SUCCEED();
} else {
const auto file_name = GetFileName(path);
R_UNLESS(file_name, FsError_PathNotFound);
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
return !strcasecmp(file_name, e.name);
});
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
*out_entry_type = FsDirEntryType_File;
R_SUCCEED();
}
}
virtual Result CreateFile(const char* path, s64 size, u32 option) {
const auto file_name = GetFileName(path);
R_UNLESS(file_name, FsError_PathNotFound);
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
return !strcasecmp(file_name, e.name);
});
R_UNLESS(it == m_entries.end(), FsError_PathAlreadyExists);
FsDirectoryEntry entry{};
std::strcpy(entry.name, file_name);
entry.type = FsDirEntryType_File;
entry.file_size = size;
m_entries.emplace_back(entry);
R_SUCCEED();
}
virtual Result DeleteFile(const char* path) {
const auto file_name = GetFileName(path);
R_UNLESS(file_name, FsError_PathNotFound);
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
return !strcasecmp(file_name, e.name);
});
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
m_entries.erase(it);
R_SUCCEED();
}
virtual Result RenameFile(const char *old_path, const char *new_path) {
const auto file_name = GetFileName(old_path);
R_UNLESS(file_name, FsError_PathNotFound);
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
return !strcasecmp(file_name, e.name);
});
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
const auto file_name_new = GetFileName(new_path);
R_UNLESS(file_name_new, FsError_PathNotFound);
const auto new_it = std::ranges::find_if(m_entries, [file_name_new](auto& e){
return !strcasecmp(file_name_new, e.name);
});
R_UNLESS(new_it == m_entries.end(), FsError_PathAlreadyExists);
std::strcpy(it->name, file_name_new);
R_SUCCEED();
}
virtual Result OpenFile(const char *path, u32 mode, FsFile *out_file) {
const auto file_name = GetFileName(path);
R_UNLESS(file_name, FsError_PathNotFound);
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
return !strcasecmp(file_name, e.name);
});
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
out_file->s.object_id = std::distance(m_entries.begin(), it);
out_file->s.own_handle = mode;
R_SUCCEED();
}
virtual Result GetFileSize(FsFile *file, s64 *out_size) {
auto& e = m_entries[file->s.object_id];
*out_size = e.file_size;
R_SUCCEED();
}
virtual Result SetFileSize(FsFile *file, s64 size) {
auto& e = m_entries[file->s.object_id];
e.file_size = size;
R_SUCCEED();
}
virtual Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) {
// stub for now as it may confuse users who think that the returned file is valid.
// the code below can be used to benchmark mtp reads.
R_THROW(FsError_NotImplemented);
// auto& e = m_entries[file->s.object_id];
// read_size = std::min<s64>(e.file_size - off, read_size);
// std::memset(buf, 0, read_size);
// *out_bytes_read = read_size;
// R_SUCCEED();
}
virtual Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) {
auto& e = m_entries[file->s.object_id];
e.file_size = std::max<s64>(e.file_size, off + write_size);
R_SUCCEED();
}
virtual void CloseFile(FsFile *file) {
std::memset(file, 0, sizeof(*file));
}
Result CreateDirectory(const char* path) override {
R_THROW(FsError_NotImplemented);
}
Result DeleteDirectoryRecursively(const char* path) override {
R_THROW(FsError_NotImplemented);
}
Result RenameDirectory(const char *old_path, const char *new_path) override {
R_THROW(FsError_NotImplemented);
}
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) override {
std::memset(out_dir, 0, sizeof(*out_dir));
R_SUCCEED();
}
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) override {
max_entries = std::min<s64>(m_entries.size()- d->s.object_id, max_entries);
std::memcpy(buf, m_entries.data() + d->s.object_id, max_entries * sizeof(*buf));
d->s.object_id += max_entries;
*out_total_entries = max_entries;
R_SUCCEED();
}
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) override {
*out_count = m_entries.size();
R_SUCCEED();
}
void CloseDirectory(FsDir *d) override {
std::memset(d, 0, sizeof(*d));
}
protected:
std::vector<FsDirectoryEntry> m_entries;
};
struct FsDevNullProxy final : FsProxyVfs {
using FsProxyVfs::FsProxyVfs;
Result GetTotalSpace(const char *path, s64 *out) override {
*out = 1024ULL * 1024ULL * 1024ULL * 256ULL;
@@ -244,83 +397,41 @@ struct FsDevNullProxy final : FsProxyBase {
*out = 1024ULL * 1024ULL * 1024ULL * 256ULL;
R_SUCCEED();
}
Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) override {
if (FixPath(path) == "/") {
*out_entry_type = FsDirEntryType_Dir;
R_SUCCEED();
} else {
*out_entry_type = FsDirEntryType_File;
R_SUCCEED();
}
}
Result CreateFile(const char* path, s64 size, u32 option) override {
R_SUCCEED();
}
Result DeleteFile(const char* path) override {
R_SUCCEED();
}
Result RenameFile(const char *old_path, const char *new_path) override {
R_SUCCEED();
}
Result OpenFile(const char *path, u32 mode, FsFile *out_file) override {
R_SUCCEED();
}
Result GetFileSize(FsFile *file, s64 *out_size) override {
*out_size = 0;
R_SUCCEED();
}
Result SetFileSize(FsFile *file, s64 size) override {
R_SUCCEED();
}
Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) override {
*out_bytes_read = 0;
R_SUCCEED();
}
Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) override {
R_SUCCEED();
}
void CloseFile(FsFile *file) override {
std::memset(file, 0, sizeof(*file));
}
Result CreateDirectory(const char* path) override {
R_SUCCEED();
}
Result DeleteDirectoryRecursively(const char* path) override {
R_SUCCEED();
}
Result RenameDirectory(const char *old_path, const char *new_path) override {
R_SUCCEED();
}
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) override {
R_SUCCEED();
}
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) override {
*out_total_entries = 0;
R_SUCCEED();
}
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) override {
*out_count = 0;
R_SUCCEED();
}
void CloseDirectory(FsDir *d) override {
std::memset(d, 0, sizeof(*d));
}
};
struct FsInstallProxy final : FsProxyBase {
using FsProxyBase::FsProxyBase;
struct FsInstallProxy final : FsProxyVfs {
using FsProxyVfs::FsProxyVfs;
Result FailedIfNotEnabled() {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
if (!g_shared_data.enabled) {
App::Notify("Please launch MTP install menu before trying to install"_i18n);
R_THROW(0x1);
R_THROW(FsError_NotImplemented);
}
R_SUCCEED();
}
// TODO: impl this.
Result IsValidFileType(const char* name) {
const char* ext = std::strrchr(name, '.');
if (!ext) {
R_THROW(FsError_NotImplemented);
}
bool found = false;
for (size_t i = 0; i < std::size(SUPPORTED_EXT); i++) {
if (!strcasecmp(ext, SUPPORTED_EXT[i])) {
found = true;
break;
}
}
if (!found) {
R_THROW(FsError_NotImplemented);
}
R_SUCCEED();
}
Result GetTotalSpace(const char *path, s64 *out) override {
if (App::GetApp()->m_install_sd.Get()) {
return fs::FsNativeContentStorage(FsContentStorageId_SdCard).GetTotalSpace("/", out);
@@ -335,140 +446,113 @@ struct FsInstallProxy final : FsProxyBase {
return fs::FsNativeContentStorage(FsContentStorageId_User).GetFreeSpace("/", out);
}
}
Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) override {
if (FixPath(path) == "/") {
*out_entry_type = FsDirEntryType_Dir;
R_SUCCEED();
} else {
*out_entry_type = FsDirEntryType_File;
R_SUCCEED();
R_TRY(FsProxyVfs::GetEntryType(path, out_entry_type));
if (*out_entry_type == FsDirEntryType_File) {
R_TRY(FailedIfNotEnabled());
}
}
Result CreateFile(const char* path, s64 size, u32 option) override {
return FailedIfNotEnabled();
}
Result DeleteFile(const char* path) override {
R_SUCCEED();
}
Result RenameFile(const char *old_path, const char *new_path) override {
Result CreateFile(const char* path, s64 size, u32 option) override {
R_TRY(FailedIfNotEnabled());
R_TRY(IsValidFileType(path));
R_TRY(FsProxyVfs::CreateFile(path, size, option));
R_SUCCEED();
}
Result OpenFile(const char *path, u32 mode, FsFile *out_file) override {
if (mode & FsOpenMode_Read) {
R_SUCCEED();
} else {
std::scoped_lock lock{g_shared_data.mutex};
if (!g_shared_data.enabled) {
R_THROW(0x1);
}
R_TRY(FailedIfNotEnabled());
R_TRY(IsValidFileType(path));
R_TRY(FsProxyVfs::OpenFile(path, mode, out_file));
log_write("[MTP] done file open: %s mode: 0x%X\n", path, mode);
const char* ext = std::strrchr(path, '.');
if (!ext) {
R_THROW(0x1);
}
bool found = false;
for (size_t i = 0; i < std::size(SUPPORTED_EXT); i++) {
if (!strcasecmp(ext, SUPPORTED_EXT[i])) {
found = true;
break;
}
}
if (!found) {
R_THROW(0x1);
}
if (mode & FsOpenMode_Write) {
const auto& e = m_entries[out_file->s.object_id];
// check if we already have this file queued.
auto it = std::ranges::find(g_shared_data.queued_files, path);
if (it != g_shared_data.queued_files.cend()) {
R_THROW(0x1);
}
g_shared_data.queued_files.push_back(path);
log_write("[MTP] checking if empty\n");
R_UNLESS(g_shared_data.current_file.empty(), FsError_NotImplemented);
log_write("[MTP] is empty\n");
g_shared_data.current_file = e.name;
on_thing();
}
on_thing();
log_write("[MTP] got file: %s\n", path);
R_SUCCEED();
}
Result GetFileSize(FsFile *file, s64 *out_size) override {
*out_size = 0;
R_SUCCEED();
}
Result SetFileSize(FsFile *file, s64 size) override {
R_SUCCEED();
}
Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) override {
*out_bytes_read = 0;
R_SUCCEED();
}
Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) override {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
if (!g_shared_data.enabled) {
R_THROW(0x1);
log_write("[MTP] failing as not enabled\n");
R_THROW(FsError_NotImplemented);
}
if (!g_shared_data.on_write || !g_shared_data.on_write(g_shared_data.user, buf, write_size)) {
R_THROW(0x1);
if (!g_shared_data.on_write || !g_shared_data.on_write(buf, write_size)) {
log_write("[MTP] failing as not written\n");
R_THROW(FsError_NotImplemented);
}
R_TRY(FsProxyVfs::WriteFile(file, off, buf, write_size, option));
R_SUCCEED();
}
void CloseFile(FsFile *file) override {
bool update{};
{
log_write("[MTP] closing file\n");
std::scoped_lock lock{g_shared_data.mutex};
log_write("[MTP] closing valid file\n");
SCOPED_MUTEX(&g_shared_data.mutex);
if (file->s.own_handle & FsOpenMode_Write) {
log_write("[MTP] closing current file\n");
if (g_shared_data.on_close) {
g_shared_data.on_close();
}
log_write("[MTP] closing current file\n");
if (g_shared_data.on_close) {
g_shared_data.on_close(g_shared_data.user);
g_shared_data.in_progress = false;
g_shared_data.current_file.clear();
update = true;
}
g_shared_data.in_progress = false;
g_shared_data.queued_files.clear();
}
on_thing();
std::memset(file, 0, sizeof(*file));
}
if (update) {
on_thing();
}
Result CreateDirectory(const char* path) override {
R_SUCCEED();
}
Result DeleteDirectoryRecursively(const char* path) override {
R_SUCCEED();
}
Result RenameDirectory(const char *old_path, const char *new_path) override {
R_SUCCEED();
}
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) override {
R_SUCCEED();
}
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) override {
*out_total_entries = 0;
R_SUCCEED();
}
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) override {
*out_count = 0;
R_SUCCEED();
}
void CloseDirectory(FsDir *d) override {
std::memset(d, 0, sizeof(*d));
FsProxyVfs::CloseFile(file);
}
};
::haze::FsEntries g_fs_entries{};
void haze_callback(const ::haze::CallbackData *data) {
auto& e = *data;
switch (e.type) {
case ::haze::CallbackType_OpenSession: log_write("[LIBHAZE] Opening Session\n"); break;
case ::haze::CallbackType_CloseSession: log_write("[LIBHAZE] Closing Session\n"); break;
case ::haze::CallbackType_CreateFile: log_write("[LIBHAZE] Creating File: %s\n", e.file.filename); break;
case ::haze::CallbackType_DeleteFile: log_write("[LIBHAZE] Deleting File: %s\n", e.file.filename); break;
case ::haze::CallbackType_RenameFile: log_write("[LIBHAZE] Rename File: %s -> %s\n", e.rename.filename, e.rename.newname); break;
case ::haze::CallbackType_RenameFolder: log_write("[LIBHAZE] Rename Folder: %s -> %s\n", e.rename.filename, e.rename.newname); break;
case ::haze::CallbackType_CreateFolder: log_write("[LIBHAZE] Creating Folder: %s\n", e.file.filename); break;
case ::haze::CallbackType_DeleteFolder: log_write("[LIBHAZE] Deleting Folder: %s\n", e.file.filename); break;
case ::haze::CallbackType_ReadBegin: log_write("[LIBHAZE] Reading File Begin: %s \n", e.file.filename); break;
case ::haze::CallbackType_ReadProgress: log_write("\t[LIBHAZE] Reading File: offset: %lld size: %lld\n", e.progress.offset, e.progress.size); break;
case ::haze::CallbackType_ReadEnd: log_write("[LIBHAZE] Reading File Finished: %s\n", e.file.filename); break;
case ::haze::CallbackType_WriteBegin: log_write("[LIBHAZE] Writing File Begin: %s \n", e.file.filename); break;
case ::haze::CallbackType_WriteProgress: log_write("\t[LIBHAZE] Writing File: offset: %lld size: %lld\n", e.progress.offset, e.progress.size); break;
case ::haze::CallbackType_WriteEnd: log_write("[LIBHAZE] Writing File Finished: %s\n", e.file.filename); break;
}
App::NotifyFlashLed();
}
} // namespace
bool Init() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
if (g_is_running) {
log_write("[MTP] already enabled, cannot open\n");
return false;
@@ -481,7 +565,7 @@ bool Init() {
g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)"));
g_should_exit = false;
if (!::haze::Initialize(haze_callback, PRIO_PREEMPTIVE, 2, g_fs_entries)) {
if (!::haze::Initialize(haze_callback, THREAD_PRIO, THREAD_CORE, g_fs_entries)) {
return false;
}
@@ -490,7 +574,7 @@ bool Init() {
}
void Exit() {
std::scoped_lock lock{g_mutex};
SCOPED_MUTEX(&g_mutex);
if (!g_is_running) {
return;
}
@@ -503,9 +587,8 @@ void Exit() {
log_write("[MTP] exitied\n");
}
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
std::scoped_lock lock{g_shared_data.mutex};
g_shared_data.user = user;
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
SCOPED_MUTEX(&g_shared_data.mutex);
g_shared_data.on_start = on_start;
g_shared_data.on_write = on_write;
g_shared_data.on_close = on_close;
@@ -513,7 +596,7 @@ void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_writ
}
void DisableInstallMode() {
std::scoped_lock lock{g_shared_data.mutex};
SCOPED_MUTEX(&g_shared_data.mutex);
g_shared_data.enabled = false;
}

View File

@@ -1,166 +1,25 @@
#include "ui/menus/ftp_menu.hpp"
#include "yati/yati.hpp"
#include "app.hpp"
#include "defines.hpp"
#include "log.hpp"
#include "ui/nvg_util.hpp"
#include "i18n.hpp"
#include "ftpsrv_helper.hpp"
#include <cstring>
#include <algorithm>
namespace sphaira::ui::menu::ftp {
namespace {
constexpr u64 MAX_BUFFER_SIZE = 1024*1024*32;
constexpr u64 SLEEPNS = 1000;
volatile bool IN_PUSH_THREAD{};
bool OnInstallStart(void* user, const char* path) {
auto menu = (Menu*)user;
log_write("[INSTALL] inside OnInstallStart()\n");
for (;;) {
mutexLock(&menu->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
if (menu->m_state != State::Progress) {
break;
}
if (menu->GetToken().stop_requested()) {
return false;
}
svcSleepThread(1e+6);
}
log_write("[INSTALL] OnInstallStart() got state: %u\n", (u8)menu->m_state);
if (menu->m_source) {
log_write("[INSTALL] OnInstallStart() we have source\n");
for (;;) {
mutexLock(&menu->m_source->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&menu->m_source->m_mutex));
if (!IN_PUSH_THREAD) {
break;
}
if (menu->GetToken().stop_requested()) {
return false;
}
svcSleepThread(1e+6);
}
log_write("[INSTALL] OnInstallStart() stopped polling source\n");
}
log_write("[INSTALL] OnInstallStart() doing make_shared\n");
menu->m_source = std::make_shared<StreamFtp>(path, menu->GetToken());
mutexLock(&menu->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
menu->m_state = State::Connected;
log_write("[INSTALL] OnInstallStart() done make shared\n");
return true;
}
bool OnInstallWrite(void* user, const void* buf, size_t size) {
auto menu = (Menu*)user;
return menu->m_source->Push(buf, size);
}
void OnInstallClose(void* user) {
auto menu = (Menu*)user;
menu->m_source->Disable();
}
} // namespace
StreamFtp::StreamFtp(const fs::FsPath& path, std::stop_token token) {
m_path = path;
m_token = token;
m_buffer.reserve(MAX_BUFFER_SIZE);
m_active = true;
}
Result StreamFtp::ReadChunk(void* buf, s64 size, u64* bytes_read) {
while (!m_token.stop_requested()) {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (m_buffer.empty()) {
if (!m_active) {
break;
}
svcSleepThread(SLEEPNS);
} else {
size = std::min<s64>(size, m_buffer.size());
std::memcpy(buf, m_buffer.data(), size);
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + size);
*bytes_read = size;
R_SUCCEED();
}
}
return 0x1;
}
bool StreamFtp::Push(const void* buf, s64 size) {
IN_PUSH_THREAD = true;
ON_SCOPE_EXIT(IN_PUSH_THREAD = false);
while (!m_token.stop_requested()) {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (!m_active) {
break;
}
if (m_buffer.size() + size >= MAX_BUFFER_SIZE) {
svcSleepThread(SLEEPNS);
} else {
const auto offset = m_buffer.size();
m_buffer.resize(offset + size);
std::memcpy(m_buffer.data() + offset, buf, size);
return true;
}
}
return false;
}
void StreamFtp::Disable() {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
m_active = false;
}
Menu::Menu(u32 flags) : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});
SetAction(Button::X, Action{"Options"_i18n, [this](){
App::DisplayInstallOptions(false);
}});
App::SetAutoSleepDisabled(true);
mutexInit(&m_mutex);
Menu::Menu(u32 flags) : stream::Menu{"FTP Install"_i18n, flags} {
m_was_ftp_enabled = App::GetFtpEnable();
if (!m_was_ftp_enabled) {
log_write("[FTP] wasn't enabled, forcefully enabling\n");
App::SetFtpEnable(true);
}
ftpsrv::InitInstallMode(this, OnInstallStart, OnInstallWrite, OnInstallClose);
ftpsrv::InitInstallMode(
[this](const char* path){ return OnInstallStart(path); },
[this](const void *buf, size_t size){ return OnInstallWrite(buf, size); },
[this](){ return OnInstallClose(); }
);
m_port = ftpsrv::GetPort();
m_anon = ftpsrv::IsAnon();
@@ -173,71 +32,19 @@ Menu::Menu(u32 flags) : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n, flags} {
Menu::~Menu() {
// signal for thread to exit and wait.
ftpsrv::DisableInstallMode();
m_stop_source.request_stop();
if (m_source) {
m_source->Disable();
}
if (!m_was_ftp_enabled) {
log_write("[FTP] disabling on exit\n");
App::SetFtpEnable(false);
}
App::SetAutoSleepDisabled(false);
log_write("closing data!!!!\n");
}
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
switch (m_state) {
case State::None:
break;
case State::Connected:
log_write("set to progress\n");
m_state = State::Progress;
log_write("got connection\n");
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
log_write("inside progress box\n");
const auto rc = yati::InstallFromSource(pbox, m_source, m_source->m_path);
if (R_FAILED(rc)) {
m_source->Disable();
R_THROW(rc);
}
R_SUCCEED();
}, [this](Result rc){
App::PushErrorBox(rc, "Ftp install failed!"_i18n);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (R_SUCCEEDED(rc)) {
App::Notify("Ftp install success!"_i18n);
m_state = State::Done;
} else {
m_state = State::Failed;
}
}));
break;
case State::Progress:
case State::Done:
case State::Failed:
break;
}
stream::Menu::Update(controller, touch);
}
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
stream::Menu::Draw(vg, theme);
const auto pdata = GetPolledData();
if (pdata.ip) {
@@ -257,6 +64,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
float bounds[4];
nvgFontSize(vg, font_size);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
// note: textbounds strips spaces...todo: use nvgTextGlyphPositions() instead.
#define draw(key, ...) \
@@ -286,31 +94,10 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
#undef draw
switch (m_state) {
case State::None:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Waiting for connection..."_i18n.c_str());
break;
case State::Connected:
break;
case State::Progress:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Transferring data..."_i18n.c_str());
break;
case State::Done:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Press B to exit..."_i18n.c_str());
break;
case State::Failed:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to install via FTP, press B to exit..."_i18n.c_str());
break;
}
}
void Menu::OnFocusGained() {
MenuBase::OnFocusGained();
void Menu::OnDisableInstallMode() {
ftpsrv::DisableInstallMode();
}
} // namespace sphaira::ui::menu::ftp

View File

@@ -0,0 +1,225 @@
#include "ui/menus/install_stream_menu_base.hpp"
#include "yati/yati.hpp"
#include "app.hpp"
#include "defines.hpp"
#include "log.hpp"
#include "ui/nvg_util.hpp"
#include "i18n.hpp"
#include <cstring>
namespace sphaira::ui::menu::stream {
namespace {
enum class InstallState {
None,
Progress,
Finished,
};
constexpr u64 MAX_BUFFER_SIZE = 1024ULL*1024ULL*8ULL;
constexpr u64 MAX_BUFFER_RESERVE_SIZE = 1024ULL*1024ULL*32ULL;
volatile InstallState INSTALL_STATE{InstallState::None};
} // namespace
Stream::Stream(const fs::FsPath& path, std::stop_token token) {
m_path = path;
m_token = token;
m_active = true;
m_buffer.reserve(MAX_BUFFER_RESERVE_SIZE);
mutexInit(&m_mutex);
condvarInit(&m_can_read);
}
Result Stream::ReadChunk(void* buf, s64 size, u64* bytes_read) {
log_write("[Stream::ReadChunk] inside\n");
ON_SCOPE_EXIT(
log_write("[Stream::ReadChunk] exiting\n");
);
while (!m_token.stop_requested()) {
SCOPED_MUTEX(&m_mutex);
if (m_active && m_buffer.empty()) {
condvarWait(&m_can_read, &m_mutex);
}
if ((!m_active && m_buffer.empty()) || m_token.stop_requested()) {
break;
}
size = std::min<s64>(size, m_buffer.size());
std::memcpy(buf, m_buffer.data(), size);
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + size);
*bytes_read = size;
R_SUCCEED();
}
log_write("[Stream::ReadChunk] failed to read\n");
R_THROW(Result_TransferCancelled);
}
bool Stream::Push(const void* buf, s64 size) {
log_write("[Stream::Push] inside\n");
ON_SCOPE_EXIT(
log_write("[Stream::Push] exiting\n");
);
while (!m_token.stop_requested()) {
if (INSTALL_STATE == InstallState::Finished) {
log_write("[Stream::Push] install has finished\n");
return true;
}
// don't use condivar here as windows mtp is very broken.
// stalling for too longer (3s+) and having too varied transfer speeds
// results in windows stalling the transfer for 1m until it kills it via timeout.
// the workaround is to always accept new data, but stall for 1s.
SCOPED_MUTEX(&m_mutex);
if (m_active && m_buffer.size() >= MAX_BUFFER_SIZE) {
// unlock the mutex and wait for 1s to bring transfer speed down to 1MiB/s.
mutexUnlock(&m_mutex);
ON_SCOPE_EXIT(mutexLock(&m_mutex));
svcSleepThread(1e+9);
}
if (!m_active) {
log_write("[Stream::Push] file not active\n");
break;
}
const auto offset = m_buffer.size();
m_buffer.resize(offset + size);
std::memcpy(m_buffer.data() + offset, buf, size);
condvarWakeOne(&m_can_read);
return true;
}
log_write("[Stream::Push] failed to push\n");
return false;
}
void Stream::Disable() {
log_write("[Stream::Disable] disabling file\n");
SCOPED_MUTEX(&m_mutex);
m_active = false;
condvarWakeOne(&m_can_read);
}
Menu::Menu(const std::string& title, u32 flags) : MenuBase{title, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});
SetAction(Button::X, Action{"Options"_i18n, [this](){
App::DisplayInstallOptions(false);
}});
App::SetAutoSleepDisabled(true);
mutexInit(&m_mutex);
INSTALL_STATE = InstallState::None;
}
Menu::~Menu() {
// signal for thread to exit and wait.
m_stop_source.request_stop();
if (m_source) {
m_source->Disable();
}
App::SetAutoSleepDisabled(false);
}
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
SCOPED_MUTEX(&m_mutex);
if (m_state == State::Connected) {
m_state = State::Progress;
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, m_source->GetPath(), [this](auto pbox) -> Result {
INSTALL_STATE = InstallState::Progress;
const auto rc = yati::InstallFromSource(pbox, m_source, m_source->GetPath());
INSTALL_STATE = InstallState::Finished;
if (R_FAILED(rc)) {
m_source->Disable();
R_THROW(rc);
}
R_SUCCEED();
}, [this](Result rc){
App::PushErrorBox(rc, "Install failed!"_i18n);
SCOPED_MUTEX(&m_mutex);
if (R_SUCCEEDED(rc)) {
App::Notify("Install success!"_i18n);
m_state = State::Done;
} else {
m_state = State::Failed;
OnDisableInstallMode();
}
}));
}
}
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
SCOPED_MUTEX(&m_mutex);
switch (m_state) {
case State::None:
case State::Done:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder"_i18n.c_str());
break;
case State::Connected:
case State::Progress:
break;
case State::Failed:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to install, press B to exit..."_i18n.c_str());
break;
}
}
bool Menu::OnInstallStart(const char* path) {
log_write("[Menu::OnInstallStart] inside\n");
if (INSTALL_STATE == InstallState::Progress) {
log_write("[Menu::OnInstallStart] already in progress\n");
return false;
}
SCOPED_MUTEX(&m_mutex);
m_source = std::make_shared<Stream>(path, GetToken());
INSTALL_STATE = InstallState::None;
m_state = State::Connected;
log_write("[Menu::OnInstallStart] exiting\n");
return true;
}
bool Menu::OnInstallWrite(const void* buf, size_t size) {
log_write("[Menu::OnInstallWrite] inside\n");
return m_source->Push(buf, size);
}
void Menu::OnInstallClose() {
log_write("[Menu::OnInstallClose] inside\n");
m_source->Disable();
// wait until the install has finished before returning.
while (INSTALL_STATE == InstallState::Progress) {
svcSleepThread(1e+7);
}
}
} // namespace sphaira::ui::menu::stream

View File

@@ -1,5 +1,4 @@
#include "ui/menus/mtp_menu.hpp"
#include "yati/yati.hpp"
#include "usb/usbds.hpp"
#include "app.hpp"
#include "defines.hpp"
@@ -7,16 +6,10 @@
#include "ui/nvg_util.hpp"
#include "i18n.hpp"
#include "haze_helper.hpp"
#include <cstring>
#include <algorithm>
namespace sphaira::ui::menu::mtp {
namespace {
constexpr u64 MAX_BUFFER_SIZE = 1024*1024*32;
constexpr u64 SLEEPNS = 1000;
volatile bool IN_PUSH_THREAD{};
auto GetUsbStateStr(UsbState state) -> const char* {
switch (state) {
case UsbState_Detached: return "Detached";
@@ -44,173 +37,34 @@ auto GetUsbSpeedStr(UsbDeviceSpeed speed) -> const char* {
return "Unknown";
}
bool OnInstallStart(void* user, const char* path) {
auto menu = (Menu*)user;
log_write("[INSTALL] inside OnInstallStart()\n");
for (;;) {
mutexLock(&menu->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
if (menu->m_state != State::Progress) {
break;
}
if (menu->GetToken().stop_requested()) {
return false;
}
svcSleepThread(1e+6);
}
log_write("[INSTALL] OnInstallStart() got state: %u\n", (u8)menu->m_state);
if (menu->m_source) {
log_write("[INSTALL] OnInstallStart() we have source\n");
for (;;) {
mutexLock(&menu->m_source->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&menu->m_source->m_mutex));
if (!IN_PUSH_THREAD) {
break;
}
if (menu->GetToken().stop_requested()) {
return false;
}
svcSleepThread(1e+6);
}
log_write("[INSTALL] OnInstallStart() stopped polling source\n");
}
log_write("[INSTALL] OnInstallStart() doing make_shared\n");
menu->m_source = std::make_shared<StreamFtp>(path, menu->GetToken());
mutexLock(&menu->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
menu->m_state = State::Connected;
log_write("[INSTALL] OnInstallStart() done make shared\n");
return true;
}
bool OnInstallWrite(void* user, const void* buf, size_t size) {
auto menu = (Menu*)user;
return menu->m_source->Push(buf, size);
}
void OnInstallClose(void* user) {
auto menu = (Menu*)user;
menu->m_source->Disable();
}
} // namespace
StreamFtp::StreamFtp(const fs::FsPath& path, std::stop_token token) {
m_path = path;
m_token = token;
m_buffer.reserve(MAX_BUFFER_SIZE);
m_active = true;
}
Result StreamFtp::ReadChunk(void* buf, s64 size, u64* bytes_read) {
while (!m_token.stop_requested()) {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (m_buffer.empty()) {
if (!m_active) {
break;
}
svcSleepThread(SLEEPNS);
} else {
size = std::min<s64>(size, m_buffer.size());
std::memcpy(buf, m_buffer.data(), size);
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + size);
*bytes_read = size;
R_SUCCEED();
}
}
return 0x1;
}
bool StreamFtp::Push(const void* buf, s64 size) {
IN_PUSH_THREAD = true;
ON_SCOPE_EXIT(IN_PUSH_THREAD = false);
while (!m_token.stop_requested()) {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (!m_active) {
break;
}
if (m_buffer.size() + size >= MAX_BUFFER_SIZE) {
svcSleepThread(SLEEPNS);
} else {
const auto offset = m_buffer.size();
m_buffer.resize(offset + size);
std::memcpy(m_buffer.data() + offset, buf, size);
return true;
}
}
return false;
}
void StreamFtp::Disable() {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
m_active = false;
}
Menu::Menu(u32 flags) : MenuBase{"MTP Install"_i18n, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});
SetAction(Button::X, Action{"Options"_i18n, [this](){
App::DisplayInstallOptions(false);
}});
App::SetAutoSleepDisabled(true);
mutexInit(&m_mutex);
Menu::Menu(u32 flags) : stream::Menu{"MTP Install"_i18n, flags} {
m_was_mtp_enabled = App::GetMtpEnable();
if (!m_was_mtp_enabled) {
log_write("[MTP] wasn't enabled, forcefully enabling\n");
App::SetMtpEnable(true);
}
haze::InitInstallMode(this, OnInstallStart, OnInstallWrite, OnInstallClose);
haze::InitInstallMode(
[this](const char* path){ return OnInstallStart(path); },
[this](const void *buf, size_t size){ return OnInstallWrite(buf, size); },
[this](){ return OnInstallClose(); }
);
}
Menu::~Menu() {
// signal for thread to exit and wait.
haze::DisableInstallMode();
m_stop_source.request_stop();
if (m_source) {
m_source->Disable();
}
if (!m_was_mtp_enabled) {
log_write("[MTP] disabling on exit\n");
App::SetMtpEnable(false);
}
App::SetAutoSleepDisabled(false);
log_write("closing data!!!!\n");
}
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
stream::Menu::Update(controller, touch);
static TimeStamp poll_ts;
if (poll_ts.GetSeconds() >= 1) {
@@ -226,65 +80,10 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
std::snprintf(buf, sizeof(buf), "State: %s | Speed: %s", i18n::get(GetUsbStateStr(state)).c_str(), i18n::get(GetUsbSpeedStr(speed)).c_str());
SetSubHeading(buf);
}
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (m_state == State::Connected) {
log_write("set to progress\n");
m_state = State::Progress;
log_write("got connection\n");
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
log_write("inside progress box\n");
const auto rc = yati::InstallFromSource(pbox, m_source, m_source->m_path);
if (R_FAILED(rc)) {
m_source->Disable();
R_THROW(rc);
}
R_SUCCEED();
}, [this](Result rc){
App::PushErrorBox(rc, "MTP install failed!"_i18n);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (R_SUCCEEDED(rc)) {
App::Notify("MTP install success!"_i18n);
m_state = State::Done;
} else {
m_state = State::Failed;
haze::DisableInstallMode();
}
}));
}
}
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
switch (m_state) {
case State::None:
case State::Done:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder on PC"_i18n.c_str());
break;
case State::Connected:
case State::Progress:
break;
case State::Failed:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to install via MTP, press B to exit..."_i18n.c_str());
break;
}
}
void Menu::OnFocusGained() {
MenuBase::OnFocusGained();
void Menu::OnDisableInstallMode() {
haze::DisableInstallMode();
}
} // namespace sphaira::ui::menu::mtp

View File

@@ -166,12 +166,19 @@ Result GetTicketDataOffset(std::span<const u8> ticket, u64& out) {
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out) {
u64 data_off;
R_TRY(GetTicketDataOffset(ticket, data_off));
if (ticket.size() < data_off + sizeof(*out)) {
log_write("[ES] invalid ticket size: %zu vs %zu\n", ticket.size(), data_off + sizeof(*out));
R_THROW(Result_EsBadTicketSize);
}
std::memcpy(out, ticket.data() + data_off, sizeof(*out));
// validate ticket data.
log_write("[ES] validating ticket data\n");
R_UNLESS(out->ticket_version1 == 0x2, Result_InvalidTicketVersion); // must be version 2.
R_UNLESS(out->title_key_type == es::TicketTitleKeyType_Common || out->title_key_type == es::TicketTitleKeyType_Personalized, Result_InvalidTicketKeyType);
R_UNLESS(out->master_key_revision <= 0x20, Result_InvalidTicketKeyRevision);
log_write("[ES] valid ticket data\n");
R_SUCCEED();
}

View File

@@ -307,8 +307,10 @@ void ThreadData::WakeAllThreads() {
Result ThreadData::Read(void* buf, s64 size, u64* bytes_read) {
size = std::min<s64>(size, nca->size - read_offset);
const auto rc = yati->source->Read(buf, nca->offset + read_offset, size, bytes_read);
read_offset += *bytes_read;
R_TRY(rc);
R_UNLESS(size == *bytes_read, Result_YatiInvalidNcaReadSize);
read_offset += *bytes_read;
return rc;
}