diff --git a/assets/romfs/i18n/de.json b/assets/romfs/i18n/de.json index 97b4fee..67be579 100644 --- a/assets/romfs/i18n/de.json +++ b/assets/romfs/i18n/de.json @@ -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!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/en.json b/assets/romfs/i18n/en.json index 7f27e88..604e333 100644 --- a/assets/romfs/i18n/en.json +++ b/assets/romfs/i18n/en.json @@ -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!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/es.json b/assets/romfs/i18n/es.json index a2fb420..dffee61 100644 --- a/assets/romfs/i18n/es.json +++ b/assets/romfs/i18n/es.json @@ -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!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/fr.json b/assets/romfs/i18n/fr.json index 7bb4ebf..99daf64 100644 --- a/assets/romfs/i18n/fr.json +++ b/assets/romfs/i18n/fr.json @@ -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...", diff --git a/assets/romfs/i18n/it.json b/assets/romfs/i18n/it.json index d878361..0884ba2 100644 --- a/assets/romfs/i18n/it.json +++ b/assets/romfs/i18n/it.json @@ -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!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/ja.json b/assets/romfs/i18n/ja.json index 66540fc..4188605 100644 --- a/assets/romfs/i18n/ja.json +++ b/assets/romfs/i18n/ja.json @@ -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!": "ページのロードエラー" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/ko.json b/assets/romfs/i18n/ko.json index 80ea74d..deb935f 100644 --- a/assets/romfs/i18n/ko.json +++ b/assets/romfs/i18n/ko.json @@ -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!": "페이지 로딩 오류!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/nl.json b/assets/romfs/i18n/nl.json index db99115..22d2081 100644 --- a/assets/romfs/i18n/nl.json +++ b/assets/romfs/i18n/nl.json @@ -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!": "" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/pt.json b/assets/romfs/i18n/pt.json index 23c9aff..a79b477 100644 --- a/assets/romfs/i18n/pt.json +++ b/assets/romfs/i18n/pt.json @@ -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", diff --git a/assets/romfs/i18n/ru.json b/assets/romfs/i18n/ru.json index bf31d5d..10e46b8 100644 --- a/assets/romfs/i18n/ru.json +++ b/assets/romfs/i18n/ru.json @@ -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...": "Подключено, ожидание списка файлов...", diff --git a/assets/romfs/i18n/se.json b/assets/romfs/i18n/se.json index 8c2b3d2..ee58ce8 100644 --- a/assets/romfs/i18n/se.json +++ b/assets/romfs/i18n/se.json @@ -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!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/uk.json b/assets/romfs/i18n/uk.json index 265dbb1..e616849 100644 --- a/assets/romfs/i18n/uk.json +++ b/assets/romfs/i18n/uk.json @@ -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!": "Помилка завантаження сторінки!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/vi.json b/assets/romfs/i18n/vi.json index 5d28abc..bad195a 100644 --- a/assets/romfs/i18n/vi.json +++ b/assets/romfs/i18n/vi.json @@ -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!" -} \ No newline at end of file +} diff --git a/assets/romfs/i18n/zh.json b/assets/romfs/i18n/zh.json index e2bba18..159afde 100644 --- a/assets/romfs/i18n/zh.json +++ b/assets/romfs/i18n/zh.json @@ -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...": "已连接,正在等待文件列表...", diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index 359f22d..b8b31a7 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -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 diff --git a/sphaira/include/defines.hpp b/sphaira/include/defines.hpp index 444056f..d54be0d 100644 --- a/sphaira/include/defines.hpp +++ b/sphaira/include/defines.hpp @@ -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)) diff --git a/sphaira/include/ftpsrv_helper.hpp b/sphaira/include/ftpsrv_helper.hpp index 6357f9c..5482c23 100644 --- a/sphaira/include/ftpsrv_helper.hpp +++ b/sphaira/include/ftpsrv_helper.hpp @@ -7,11 +7,11 @@ namespace sphaira::ftpsrv { bool Init(); void Exit(); -using OnInstallStart = std::function; -using OnInstallWrite = std::function; -using OnInstallClose = std::function; +using OnInstallStart = std::function; +using OnInstallWrite = std::function; +using OnInstallClose = std::function; -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(); diff --git a/sphaira/include/haze_helper.hpp b/sphaira/include/haze_helper.hpp index a2af04d..d958a4f 100644 --- a/sphaira/include/haze_helper.hpp +++ b/sphaira/include/haze_helper.hpp @@ -7,11 +7,11 @@ namespace sphaira::haze { bool Init(); void Exit(); -using OnInstallStart = std::function; -using OnInstallWrite = std::function; -using OnInstallClose = std::function; +using OnInstallStart = std::function; +using OnInstallWrite = std::function; +using OnInstallClose = std::function; -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 diff --git a/sphaira/include/ui/menus/ftp_menu.hpp b/sphaira/include/ui/menus/ftp_menu.hpp index 99d65c2..27ece1d 100644 --- a/sphaira/include/ui/menus/ftp_menu.hpp +++ b/sphaira/include/ui/menus/ftp_menu.hpp @@ -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 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 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{}; diff --git a/sphaira/include/ui/menus/install_stream_menu_base.hpp b/sphaira/include/ui/menus/install_stream_menu_base.hpp new file mode 100644 index 0000000..aa41d9d --- /dev/null +++ b/sphaira/include/ui/menus/install_stream_menu_base.hpp @@ -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; +using OnInstallWrite = std::function; +using OnInstallClose = std::function; + +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 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 m_source{}; + Thread m_thread{}; + Mutex m_mutex{}; + State m_state{State::None}; +}; + +} // namespace sphaira::ui::menu::stream diff --git a/sphaira/include/ui/menus/mtp_menu.hpp b/sphaira/include/ui/menus/mtp_menu.hpp index 31184e5..acff3ff 100644 --- a/sphaira/include/ui/menus/mtp_menu.hpp +++ b/sphaira/include/ui/menus/mtp_menu.hpp @@ -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 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 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{}; }; diff --git a/sphaira/include/yati/nx/es.hpp b/sphaira/include/yati/nx/es.hpp index 7261714..994e7ba 100644 --- a/sphaira/include/yati/nx/es.hpp +++ b/sphaira/include/yati/nx/es.hpp @@ -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]; diff --git a/sphaira/source/ftpsrv_helper.cpp b/sphaira/source/ftpsrv_helper.cpp index b7a5102..9ffa195 100644 --- a/sphaira/source/ftpsrv_helper.cpp +++ b/sphaira/source/ftpsrv_helper.cpp @@ -4,7 +4,6 @@ #include "fs.hpp" #include "log.hpp" -#include #include #include #include @@ -16,8 +15,7 @@ namespace sphaira::ftpsrv { namespace { struct InstallSharedData { - std::mutex mutex; - + Mutex mutex; std::deque 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(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(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(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(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; } diff --git a/sphaira/source/haze_helper.cpp b/sphaira/source/haze_helper.cpp index 34a011e..aa8ea30 100644 --- a/sphaira/source/haze_helper.cpp +++ b/sphaira/source/haze_helper.cpp @@ -6,7 +6,6 @@ #include "evman.hpp" #include "i18n.hpp" -#include #include #include @@ -14,8 +13,8 @@ namespace sphaira::haze { namespace { struct InstallSharedData { - std::mutex mutex; - std::deque 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 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(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(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(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 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("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; } diff --git a/sphaira/source/ui/menus/ftp_menu.cpp b/sphaira/source/ui/menus/ftp_menu.cpp index b6033aa..9872dd5 100644 --- a/sphaira/source/ui/menus/ftp_menu.cpp +++ b/sphaira/source/ui/menus/ftp_menu.cpp @@ -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 -#include 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(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(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(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 diff --git a/sphaira/source/ui/menus/install_stream_menu_base.cpp b/sphaira/source/ui/menus/install_stream_menu_base.cpp new file mode 100644 index 0000000..b1182e2 --- /dev/null +++ b/sphaira/source/ui/menus/install_stream_menu_base.cpp @@ -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 + +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(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(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(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 diff --git a/sphaira/source/ui/menus/mtp_menu.cpp b/sphaira/source/ui/menus/mtp_menu.cpp index b92759e..79ab210 100644 --- a/sphaira/source/ui/menus/mtp_menu.cpp +++ b/sphaira/source/ui/menus/mtp_menu.cpp @@ -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 -#include 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(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(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(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 diff --git a/sphaira/source/yati/nx/es.cpp b/sphaira/source/yati/nx/es.cpp index 8e0390d..f44bc6f 100644 --- a/sphaira/source/yati/nx/es.cpp +++ b/sphaira/source/yati/nx/es.cpp @@ -166,12 +166,19 @@ Result GetTicketDataOffset(std::span ticket, u64& out) { Result GetTicketData(std::span 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(); } diff --git a/sphaira/source/yati/yati.cpp b/sphaira/source/yati/yati.cpp index 52bfa5c..1df7391 100644 --- a/sphaira/source/yati/yati.cpp +++ b/sphaira/source/yati/yati.cpp @@ -307,8 +307,10 @@ void ThreadData::WakeAllThreads() { Result ThreadData::Read(void* buf, s64 size, u64* bytes_read) { size = std::min(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; }