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

@@ -106,7 +106,6 @@
"Failed to download app!": "Download der App fehlgeschlagen",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -116,9 +115,9 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "Verbunden, warte auf die Dateiliste...",
@@ -420,4 +419,4 @@
"Empty!": "Keine Daten!",
"Not Ready...": "Nicht bereit...",
"Error loading page!": "Ladefehler!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "Failed to download app!",
"FTP Install": "FTP Install",
"FTP Install (EXPERIMENTAL)": "FTP Install (EXPERIMENTAL)",
"Connection Type: WiFi | Strength: ": "Connection Type: WiFi | Strength: ",
"Connection Type: Ethernet": "Connection Type: Ethernet",
"Connection Type: None": "Connection Type: None",
@@ -116,9 +115,9 @@
"Password:": "Password:",
"SSID:": "SSID:",
"Passphrase:": "Passphrase",
"Failed to install via FTP, press B to exit...": "Failed to install via FTP, press  to exit...",
"Ftp install success!": "Ftp install success!",
"Ftp install failed!": "Ftp install failed!",
"Failed to install, press B to exit...": "Failed to install via FTP, press  to exit...",
"Install success!": "Install success!",
"Install failed!": "Install failed!",
"USB Install": "USB Install",
"USB": "USB",
"Connected, waiting for file list...": "Connected, waiting for file list...",
@@ -420,4 +419,4 @@
"Empty!": "Empty!",
"Not Ready...": "Not Ready...",
"Error loading page!": "Error loading page!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "¡Error al descargar aplicativo!",
"FTP Install": "Instalación vía FTP",
"FTP Install (EXPERIMENTAL)": "Instalación vía FTP (EXPERIMIENTAL)",
"Connection Type: WiFi | Strength: ": "Tipo de conexión: WiFi | Fuerza de la señal",
"Connection Type: Ethernet": "Tipo de Conexión: Ethernet",
"Connection Type: None": "Tipo de Conexión: Ninguna",
@@ -116,9 +115,9 @@
"Password:": "Contraseña",
"SSID:": "SSID",
"Passphrase:": "Parafrase",
"Failed to install via FTP, press B to exit...": "Error al instalar vía FTP, presione B para salir…",
"Ftp install success!": "¡Instalación vía FTP satisfactoria!",
"Ftp install failed!": "¡Error en instalación vía FTP!",
"Failed to install, press B to exit...": "Error al instalar vía FTP, presione B para salir…",
"Install success!": "¡Instalación vía FTP satisfactoria!",
"Install failed!": "¡Error en instalación vía FTP!",
"USB Install": "Instalación vía USB",
"USB": "USB",
"Connected, waiting for file list...": "Conectado, esperando lista de archivos…",
@@ -420,4 +419,4 @@
"Empty!": "¡Vacío!",
"Not Ready...": "No listo aún…",
"Error loading page!": "¡Error cargando la página!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "Échec du téléchargement de l'application",
"FTP Install": "Installer contenu via FTP",
"FTP Install (EXPERIMENTAL)": "Installation via FTP (EXPERIMENTAL)",
"Connection Type: WiFi | Strength: ": "Type de connexion : WiFi / Force du signal :",
"Connection Type: Ethernet": "Type de connexion : Ethernet",
"Connection Type: None": "Type de connexion : Aucune",
@@ -116,9 +115,9 @@
"Password:": "Mot de passe :",
"SSID:": "SSID :",
"Passphrase:": "Mot de passe :",
"Failed to install via FTP, press B to exit...": "Installation via FTP échouée, appuyer sur B pour quitter...",
"Ftp install success!": "Installation via FTP réussie !",
"Ftp install failed!": "Installation via FTP échouée !",
"Failed to install, press B to exit...": "Installation via FTP échouée, appuyer sur B pour quitter...",
"Install success!": "Installation via FTP réussie !",
"Install failed!": "Installation via FTP échouée !",
"USB Install": "Installer contenu via USB",
"USB": "Installation via USB",
"Connected, waiting for file list...": "Connecté, en attente de la liste des fichiers...",

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -116,9 +115,9 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "",
@@ -420,4 +419,4 @@
"Empty!": "Vuoto!",
"Not Ready...": "Non pronto...",
"Error loading page!": "Errore nel caricare la pagina!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "アプリのダウンロードに失敗しました!",
"FTP Install": "FTPでインストール",
"FTP Install (EXPERIMENTAL)": "FTPでインストール(実験機能)",
"Connection Type: WiFi | Strength: ": "接続: WiFi | 強度: ",
"Connection Type: Ethernet": "接続: イーサネット",
"Connection Type: None": "接続: なし",
@@ -116,9 +115,9 @@
"Password:": "暗証番号:",
"SSID:": "SSID:",
"Passphrase:": "WiFi暗証番号:",
"Failed to install via FTP, press B to exit...": "FTP経由でインストールできませんでした、を押して終了します",
"Ftp install success!": "FTPインストール完了!",
"Ftp install failed!": "FTPインストール失敗!",
"Failed to install, press B to exit...": "FTP経由でインストールできませんでした、を押して終了します",
"Install success!": "FTPインストール完了!",
"Install failed!": "FTPインストール失敗!",
"USB Install": "USBインストール",
"USB": "USBインストール",
"Connected, waiting for file list...": "接続されました、ファイルリストを待っています",
@@ -420,4 +419,4 @@
"Empty!": "何も見つかりません",
"Not Ready...": "準備ができていません",
"Error loading page!": "ページのロードエラー"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "앱 다운로드 실패!",
"FTP Install": "FTP 설치",
"FTP Install (EXPERIMENTAL)": "FTP 설치 (실험실 기능)",
"Connection Type: WiFi | Strength: ": "상태: WiFi | 신호 세기: ",
"Connection Type: Ethernet": "상태: 이더넷",
"Connection Type: None": "상태: 연결 없음",
@@ -116,9 +115,9 @@
"Password:": "비밀번호:",
"SSID:": "SSID:",
"Passphrase:": "WiFi 암호:",
"Failed to install via FTP, press B to exit...": "FTP 설치 실패함, 종료하려면  버튼을 입력하세요...",
"Ftp install success!": "FTP 설치 완료!",
"Ftp install failed!": "FTP 설치 실패!",
"Failed to install, press B to exit...": "FTP 설치 실패함, 종료하려면  버튼을 입력하세요...",
"Install success!": "FTP 설치 완료!",
"Install failed!": "FTP 설치 실패!",
"USB Install": "USB 설치",
"USB": "USB 설치",
"Connected, waiting for file list...": "연결됨, 파일 목록 대기 중...",
@@ -420,4 +419,4 @@
"Empty!": "찾을 수 없습니다!",
"Not Ready...": "준비되지 않음...",
"Error loading page!": "페이지 로딩 오류!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -116,9 +115,9 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "",
@@ -420,4 +419,4 @@
"Empty!": "",
"Not Ready...": "",
"Error loading page!": ""
}
}

View File

@@ -50,7 +50,7 @@
"Misc": "Diversos",
"Misc Options": "Diversos",
"Games": "Softwares",
"Game Options": "Opções do software",
"Hide forwarders": "Ocultar atalhos forwarder",
@@ -91,7 +91,7 @@
"Delete failed!": "Remoção falhou.",
"Success": "Concluído.",
"Title cache": "Usar cache de títulos",
"Saves": "Dados salvos",
"Save Options": "Opções de dados salvos",
"Account": "Usuário",
@@ -117,7 +117,7 @@
"Are you sure you want to restore ": "Tem certeza de que quer restaurar os dados salvo de ",
"Restore successfull!": "Restauração concluída.",
"Restore failed!": "Restauração falhou.",
"Themezer": "Themezer",
"Themezer Options": "Opções do Themezer",
"Nsfw": "Temas 18+ (NSFW)",
@@ -134,7 +134,6 @@
"Failed to download app!": "falha ao baixar aplicativo.",
"FTP Install": "Instalação via FTP",
"FTP Install (EXPERIMENTAL)": "Instalação via FTP (experimental)",
"Connection Type: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ",
"Connection Type: Ethernet": "Conexão por cabo Ethernet",
"Connection Type: None": "Sem conexão",
@@ -144,9 +143,9 @@
"Password:": "Senha:",
"SSID:": "SSID:",
"Passphrase:": "Senha:",
"Failed to install via FTP, press B to exit...": "Falha ao instalar via FTP, aperte B para sair.",
"Ftp install success!": "Instalação via FTP concluída.",
"Ftp install failed!": "Instalação via FTP falhou.",
"Failed to install, press B to exit...": "Falha ao instalar via FTP, aperte B para sair.",
"Install success!": "Instalação via FTP concluída.",
"Install failed!": "Instalação via FTP falhou.",
"USB Install": "Instalação via USB",
"USB": "USB",
"Connected, waiting for file list...": "Conectado, aguardando lista de arquivos...",
@@ -213,7 +212,7 @@
"Trimming Format": "Formato do recorte",
"External Light Filter": "Filtro de luz externa",
"Load Default": "Restaurar padrão",
"Web": "Navegador de internet",
"Select URL": "Selecione uma URL",
"Enter custom URL": "Digitar URL",

View File

@@ -90,7 +90,7 @@
"Delete failed!": "Ошибка удаления!",
"Success": "Успех",
"Title cache": "Кэш заголовков",
"Saves": "Сохранения",
"Save Options": "Опции сохранений",
"Account": "Пользователь",
@@ -124,7 +124,6 @@
"Failed to download app!": "Не удалось загрузить приложение!",
"FTP Install": "Установка по FTP",
"FTP Install (EXPERIMENTAL)": "Установка по FTP (ЭКСПЕРИМЕНТАЛЬНО)",
"Connection Type: WiFi | Strength: ": "Тип подключения: WiFi | Сигнал: ",
"Connection Type: Ethernet": "Тип подключения: Ethernet",
"Connection Type: None": "Нет подключения",
@@ -134,9 +133,9 @@
"Password:": "Пароль:",
"SSID:": "SSID:",
"Passphrase:": "Пароль:",
"Failed to install via FTP, press B to exit...": "Не удалось установить по FTP, нажмите B для выхода...",
"Ftp install success!": "Установка по FTP прошла успешно!",
"Ftp install failed!": "Сбой установки по FTP!",
"Failed to install, press B to exit...": "Не удалось установить по FTP, нажмите B для выхода...",
"Install success!": "Установка по FTP прошла успешно!",
"Install failed!": "Сбой установки по FTP!",
"USB Install": "Установка по USB",
"USB": "USB",
"Connected, waiting for file list...": "Подключено, ожидание списка файлов...",

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -116,9 +115,9 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "",
@@ -420,4 +419,4 @@
"Empty!": "Tomt!",
"Not Ready...": "Inte redo...",
"Error loading page!": "Fel vid laddning av sida!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "Не вдалося завантажити програму!",
"FTP Install": "Встановлення через FTP",
"FTP Install (EXPERIMENTAL)": "Встановлення через FTP (ЕКСПЕРИМЕНТАЛЬНО)",
"Connection Type: WiFi | Strength: ": "Тип підключення: WiFi | Сила сигналу: ",
"Connection Type: Ethernet": "Тип підключення: Ethernet",
"Connection Type: None": "Тип підключення: Немає",
@@ -116,9 +115,9 @@
"Password:": "Пароль:",
"SSID:": "SSID:",
"Passphrase:": "Кодова фраза:",
"Failed to install via FTP, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
"Ftp install success!": "Встановлення через FTP успішно завершено.",
"Ftp install failed!": "Встановлення через FTP не вдалося.",
"Failed to install, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
"Install success!": "Встановлення через FTP успішно завершено.",
"Install failed!": "Встановлення через FTP не вдалося.",
"USB Install": "Встановлення через USB",
"USB": "USB",
"Connected, waiting for file list...": "Підключено, очікування списку файлів...",
@@ -420,4 +419,4 @@
"Empty!": "Пусто!",
"Not Ready...": "Не готово...",
"Error loading page!": "Помилка завантаження сторінки!"
}
}

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -116,9 +115,9 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "",
@@ -420,4 +419,4 @@
"Empty!": "Trống!",
"Not Ready...": "Chưa sẵn sàng...",
"Error loading page!": "Lỗi tải trang!"
}
}

View File

@@ -117,7 +117,6 @@
"Failed to download app!": "下载应用程序失败!",
"FTP Install": "通过 FTP 安装",
"FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)",
"Connection Type: WiFi | Strength: ": "连接类型WiFi |强度:",
"Connection Type: Ethernet": "连接类型:以太网",
"Connection Type: None": "连接类型:无",
@@ -127,9 +126,9 @@
"Password:": "密码:",
"SSID:": "网络名称:",
"Passphrase:": "密码:",
"Failed to install via FTP, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
"Ftp install success!": "通过 FTP 安装成功。",
"Ftp install failed!": "通过 FTP 安装失败。",
"Failed to install, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
"Install success!": "通过 FTP 安装成功。",
"Install failed!": "通过 FTP 安装失败。",
"USB Install": "通过 USB 安装",
"USB": "USB",
"Connected, waiting for file list...": "已连接,正在等待文件列表...",

View File

@@ -49,6 +49,7 @@ add_executable(sphaira
source/ui/menus/gc_menu.cpp
source/ui/menus/game_menu.cpp
source/ui/menus/grid_menu_base.cpp
source/ui/menus/install_stream_menu_base.cpp
source/ui/error_box.cpp
source/ui/notification.cpp
@@ -162,7 +163,7 @@ FetchContent_Declare(ftpsrv
FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG d318432
GIT_TAG af69c0a
)
FetchContent_Declare(libpulsar

View File

@@ -581,6 +581,7 @@ enum class SphairaResult : Result {
EsPersonalisedTicketDeviceIdMissmatch,
EsFailedDecryptPersonalisedTicket,
EsBadDecryptedPersonalisedTicketSize,
EsBadTicketSize,
OwoBadArgs,
@@ -717,6 +718,7 @@ enum : Result {
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket),
MAKE_SPHAIRA_RESULT_ENUM(EsBadDecryptedPersonalisedTicketSize),
MAKE_SPHAIRA_RESULT_ENUM(EsBadTicketSize),
MAKE_SPHAIRA_RESULT_ENUM(OwoBadArgs),
MAKE_SPHAIRA_RESULT_ENUM(UsbCancelled),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic),
@@ -803,4 +805,6 @@ enum : Result {
#define THREAD_AFFINITY_ALL (THREAD_AFFINITY_CORE0|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
// mutex helpers.
#define SCOPED_MUTEX(mutex) mutexLock(mutex); ON_SCOPE_EXIT(mutexUnlock(mutex))
#define SCOPED_MUTEX(mutex) \
mutexLock(mutex); \
ON_SCOPE_EXIT(mutexUnlock(mutex))

View File

@@ -7,11 +7,11 @@ namespace sphaira::ftpsrv {
bool Init();
void Exit();
using OnInstallStart = std::function<bool(void* user, const char* path)>;
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>;
using OnInstallClose = std::function<void(void* user)>;
using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void()>;
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
void DisableInstallMode();
unsigned GetPort();

View File

@@ -7,11 +7,11 @@ namespace sphaira::haze {
bool Init();
void Exit();
using OnInstallStart = std::function<bool(void* user, const char* path)>;
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>;
using OnInstallClose = std::function<void(void* user)>;
using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void()>;
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
void DisableInstallMode();
} // namespace sphaira::haze

View File

@@ -1,56 +1,20 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp"
#include "ui/menus/install_stream_menu_base.hpp"
namespace sphaira::ui::menu::ftp {
enum class State {
// not connected.
None,
// just connected, starts the transfer.
Connected,
// set whilst transfer is in progress.
Progress,
// set when the transfer is finished.
Done,
// failed to connect.
Failed,
};
struct StreamFtp final : yati::source::Stream {
StreamFtp(const fs::FsPath& path, std::stop_token token);
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
bool Push(const void* buf, s64 size);
void Disable();
// private:
fs::FsPath m_path{};
std::stop_token m_token{};
std::vector<u8> m_buffer{};
Mutex m_mutex{};
bool m_active{};
// bool m_push_exit{};
};
struct Menu final : MenuBase {
struct Menu final : stream::Menu {
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "FTP"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
// this should be private
// private:
std::shared_ptr<StreamFtp> m_source{};
Thread m_thread{};
Mutex m_mutex{};
// the below are shared across threads, lock with the above mutex!
State m_state{State::None};
void OnDisableInstallMode() override;
private:
const char* m_user{};
const char* m_pass{};
unsigned m_port{};

View File

@@ -0,0 +1,62 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp"
namespace sphaira::ui::menu::stream {
enum class State {
// not connected.
None,
// just connected, starts the transfer.
Connected,
// set whilst transfer is in progress.
Progress,
// set when the transfer is finished.
Done,
// failed to connect.
Failed,
};
using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void()>;
struct Stream final : yati::source::Stream {
Stream(const fs::FsPath& path, std::stop_token token);
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
bool Push(const void* buf, s64 size);
void Disable();
auto& GetPath() const { return m_path; }
private:
fs::FsPath m_path{};
std::stop_token m_token{};
std::vector<u8> m_buffer{};
Mutex m_mutex{};
CondVar m_can_read{};
bool m_active{};
};
struct Menu : MenuBase {
Menu(const std::string& title, u32 flags);
virtual ~Menu();
virtual void Update(Controller* controller, TouchInfo* touch);
virtual void Draw(NVGcontext* vg, Theme* theme);
virtual void OnDisableInstallMode() = 0;
protected:
bool OnInstallStart(const char* path);
bool OnInstallWrite(const void* buf, size_t size);
void OnInstallClose();
private:
std::shared_ptr<Stream> m_source{};
Thread m_thread{};
Mutex m_mutex{};
State m_state{State::None};
};
} // namespace sphaira::ui::menu::stream

View File

@@ -1,56 +1,18 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp"
#include "ui/menus/install_stream_menu_base.hpp"
namespace sphaira::ui::menu::mtp {
enum class State {
// not connected.
None,
// just connected, starts the transfer.
Connected,
// set whilst transfer is in progress.
Progress,
// set when the transfer is finished.
Done,
// failed to connect.
Failed,
};
struct StreamFtp final : yati::source::Stream {
StreamFtp(const fs::FsPath& path, std::stop_token token);
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
bool Push(const void* buf, s64 size);
void Disable();
// private:
fs::FsPath m_path{};
std::stop_token m_token{};
std::vector<u8> m_buffer{};
Mutex m_mutex{};
bool m_active{};
// bool m_push_exit{};
};
struct Menu final : MenuBase {
struct Menu final : stream::Menu {
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "MTP"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
// this should be private
// private:
std::shared_ptr<StreamFtp> m_source{};
Thread m_thread{};
Mutex m_mutex{};
// the below are shared across threads, lock with the above mutex!
State m_state{State::None};
void OnDisableInstallMode() override;
private:
bool m_was_mtp_enabled{};
};

View File

@@ -53,9 +53,8 @@ struct TicketData {
FsRightsId rights_id;
u32 account_id;
u8 _0x174[0xC];
u8 _0x180[0x140];
};
static_assert(sizeof(TicketData) == 0x2C0);
static_assert(sizeof(TicketData) == 0x180);
struct EticketRsaDeviceKey {
u8 ctr[AES_128_KEY_SIZE];

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