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", "Failed to download app!": "Download der App fehlgeschlagen",
"FTP Install": "", "FTP Install": "",
"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, press B to exit...": "",
"Ftp install success!": "", "Install success!": "",
"Ftp install failed!": "", "Install failed!": "",
"USB Install": "", "USB Install": "",
"USB": "", "USB": "",
"Connected, waiting for file list...": "Verbunden, warte auf die Dateiliste...", "Connected, waiting for file list...": "Verbunden, warte auf die Dateiliste...",

View File

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

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "¡Error al descargar aplicativo!", "Failed to download app!": "¡Error al descargar aplicativo!",
"FTP Install": "Instalación vía FTP", "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: WiFi | Strength: ": "Tipo de conexión: WiFi | Fuerza de la señal",
"Connection Type: Ethernet": "Tipo de Conexión: Ethernet", "Connection Type: Ethernet": "Tipo de Conexión: Ethernet",
"Connection Type: None": "Tipo de Conexión: Ninguna", "Connection Type: None": "Tipo de Conexión: Ninguna",
@@ -116,9 +115,9 @@
"Password:": "Contraseña", "Password:": "Contraseña",
"SSID:": "SSID", "SSID:": "SSID",
"Passphrase:": "Parafrase", "Passphrase:": "Parafrase",
"Failed to install via FTP, press B to exit...": "Error al instalar vía FTP, presione B para salir…", "Failed to install, press B to exit...": "Error al instalar vía FTP, presione B para salir…",
"Ftp install success!": "¡Instalación vía FTP satisfactoria!", "Install success!": "¡Instalación vía FTP satisfactoria!",
"Ftp install failed!": "¡Error en instalación vía FTP!", "Install failed!": "¡Error en instalación vía FTP!",
"USB Install": "Instalación vía USB", "USB Install": "Instalación vía USB",
"USB": "USB", "USB": "USB",
"Connected, waiting for file list...": "Conectado, esperando lista de archivos…", "Connected, waiting for file list...": "Conectado, esperando lista de archivos…",

View File

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

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "", "Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"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, press B to exit...": "",
"Ftp install success!": "", "Install success!": "",
"Ftp install failed!": "", "Install failed!": "",
"USB Install": "", "USB Install": "",
"USB": "", "USB": "",
"Connected, waiting for file list...": "", "Connected, waiting for file list...": "",

View File

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

View File

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

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "", "Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"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, press B to exit...": "",
"Ftp install success!": "", "Install success!": "",
"Ftp install failed!": "", "Install failed!": "",
"USB Install": "", "USB Install": "",
"USB": "", "USB": "",
"Connected, waiting for file list...": "", "Connected, waiting for file list...": "",

View File

@@ -134,7 +134,6 @@
"Failed to download app!": "falha ao baixar aplicativo.", "Failed to download app!": "falha ao baixar aplicativo.",
"FTP Install": "Instalação via FTP", "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: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ",
"Connection Type: Ethernet": "Conexão por cabo Ethernet", "Connection Type: Ethernet": "Conexão por cabo Ethernet",
"Connection Type: None": "Sem conexão", "Connection Type: None": "Sem conexão",
@@ -144,9 +143,9 @@
"Password:": "Senha:", "Password:": "Senha:",
"SSID:": "SSID:", "SSID:": "SSID:",
"Passphrase:": "Senha:", "Passphrase:": "Senha:",
"Failed to install via FTP, press B to exit...": "Falha ao instalar via FTP, aperte B para sair.", "Failed to install, press B to exit...": "Falha ao instalar via FTP, aperte B para sair.",
"Ftp install success!": "Instalação via FTP concluída.", "Install success!": "Instalação via FTP concluída.",
"Ftp install failed!": "Instalação via FTP falhou.", "Install failed!": "Instalação via FTP falhou.",
"USB Install": "Instalação via USB", "USB Install": "Instalação via USB",
"USB": "USB", "USB": "USB",
"Connected, waiting for file list...": "Conectado, aguardando lista de arquivos...", "Connected, waiting for file list...": "Conectado, aguardando lista de arquivos...",

View File

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

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "", "Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"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, press B to exit...": "",
"Ftp install success!": "", "Install success!": "",
"Ftp install failed!": "", "Install failed!": "",
"USB Install": "", "USB Install": "",
"USB": "", "USB": "",
"Connected, waiting for file list...": "", "Connected, waiting for file list...": "",

View File

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

View File

@@ -106,7 +106,6 @@
"Failed to download app!": "", "Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"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, press B to exit...": "",
"Ftp install success!": "", "Install success!": "",
"Ftp install failed!": "", "Install failed!": "",
"USB Install": "", "USB Install": "",
"USB": "", "USB": "",
"Connected, waiting for file list...": "", "Connected, waiting for file list...": "",

View File

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

View File

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

View File

@@ -581,6 +581,7 @@ enum class SphairaResult : Result {
EsPersonalisedTicketDeviceIdMissmatch, EsPersonalisedTicketDeviceIdMissmatch,
EsFailedDecryptPersonalisedTicket, EsFailedDecryptPersonalisedTicket,
EsBadDecryptedPersonalisedTicketSize, EsBadDecryptedPersonalisedTicketSize,
EsBadTicketSize,
OwoBadArgs, OwoBadArgs,
@@ -717,6 +718,7 @@ enum : Result {
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch), MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket), MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket),
MAKE_SPHAIRA_RESULT_ENUM(EsBadDecryptedPersonalisedTicketSize), MAKE_SPHAIRA_RESULT_ENUM(EsBadDecryptedPersonalisedTicketSize),
MAKE_SPHAIRA_RESULT_ENUM(EsBadTicketSize),
MAKE_SPHAIRA_RESULT_ENUM(OwoBadArgs), MAKE_SPHAIRA_RESULT_ENUM(OwoBadArgs),
MAKE_SPHAIRA_RESULT_ENUM(UsbCancelled), MAKE_SPHAIRA_RESULT_ENUM(UsbCancelled),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic), MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic),
@@ -803,4 +805,6 @@ enum : Result {
#define THREAD_AFFINITY_ALL (THREAD_AFFINITY_CORE0|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2) #define THREAD_AFFINITY_ALL (THREAD_AFFINITY_CORE0|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
// mutex helpers. // 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(); bool Init();
void Exit(); void Exit();
using OnInstallStart = std::function<bool(void* user, const char* path)>; using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>; using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void(void* user)>; 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(); void DisableInstallMode();
unsigned GetPort(); unsigned GetPort();

View File

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

View File

@@ -1,56 +1,20 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp" #include "ui/menus/install_stream_menu_base.hpp"
namespace sphaira::ui::menu::ftp { namespace sphaira::ui::menu::ftp {
enum class State { struct Menu final : stream::Menu {
// 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 {
Menu(u32 flags); Menu(u32 flags);
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "FTP"; }; auto GetShortTitle() const -> const char* override { return "FTP"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnDisableInstallMode() 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};
private:
const char* m_user{}; const char* m_user{};
const char* m_pass{}; const char* m_pass{};
unsigned m_port{}; 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 #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/install_stream_menu_base.hpp"
#include "yati/source/stream.hpp"
namespace sphaira::ui::menu::mtp { namespace sphaira::ui::menu::mtp {
enum class State { struct Menu final : stream::Menu {
// 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 {
Menu(u32 flags); Menu(u32 flags);
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "MTP"; }; auto GetShortTitle() const -> const char* override { return "MTP"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void OnDisableInstallMode() 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};
private:
bool m_was_mtp_enabled{}; bool m_was_mtp_enabled{};
}; };

View File

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

View File

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

View File

@@ -6,7 +6,6 @@
#include "evman.hpp" #include "evman.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include <mutex>
#include <algorithm> #include <algorithm>
#include <haze.h> #include <haze.h>
@@ -14,8 +13,8 @@ namespace sphaira::haze {
namespace { namespace {
struct InstallSharedData { struct InstallSharedData {
std::mutex mutex; Mutex mutex;
std::deque<std::string> queued_files; std::string current_file;
void* user; void* user;
OnInstallStart on_start; OnInstallStart on_start;
@@ -30,7 +29,7 @@ constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 2; constexpr int THREAD_CORE = 2;
volatile bool g_should_exit = false; volatile bool g_should_exit = false;
bool g_is_running{false}; bool g_is_running{false};
std::mutex g_mutex{}; Mutex g_mutex{};
InstallSharedData g_shared_data{}; InstallSharedData g_shared_data{};
const char* SUPPORTED_EXT[] = { const char* SUPPORTED_EXT[] = {
@@ -40,14 +39,14 @@ const char* SUPPORTED_EXT[] = {
// ive given up with good names. // ive given up with good names.
void on_thing() { void on_thing() {
log_write("[MTP] doing on_thing\n"); 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"); log_write("[MTP] locked on_thing\n");
if (!g_shared_data.in_progress) { 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"); 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())) { if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.current_file.c_str())) {
g_shared_data.queued_files.clear(); g_shared_data.current_file.clear();
} else { } else {
log_write("[MTP] success on new file push\n"); log_write("[MTP] success on new file push\n");
g_shared_data.in_progress = true; g_shared_data.in_progress = true;
@@ -71,7 +70,7 @@ struct FsProxyBase : ::haze::FileSystemProxyImpl {
std::strcpy(buf, path); std::strcpy(buf, path);
} }
log_write("[FixPath] %s -> %s\n", path, buf.s); // log_write("[FixPath] %s -> %s\n", path, buf.s);
return buf; return buf;
} }
@@ -233,8 +232,162 @@ private:
std::shared_ptr<fs::Fs> m_fs{}; 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; 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 { Result GetTotalSpace(const char *path, s64 *out) override {
*out = 1024ULL * 1024ULL * 1024ULL * 256ULL; *out = 1024ULL * 1024ULL * 1024ULL * 256ULL;
@@ -244,83 +397,41 @@ struct FsDevNullProxy final : FsProxyBase {
*out = 1024ULL * 1024ULL * 1024ULL * 256ULL; *out = 1024ULL * 1024ULL * 1024ULL * 256ULL;
R_SUCCEED(); 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 { struct FsInstallProxy final : FsProxyVfs {
using FsProxyBase::FsProxyBase; using FsProxyVfs::FsProxyVfs;
Result FailedIfNotEnabled() { Result FailedIfNotEnabled() {
std::scoped_lock lock{g_shared_data.mutex}; SCOPED_MUTEX(&g_shared_data.mutex);
if (!g_shared_data.enabled) { if (!g_shared_data.enabled) {
App::Notify("Please launch MTP install menu before trying to install"_i18n); App::Notify("Please launch MTP install menu before trying to install"_i18n);
R_THROW(0x1); R_THROW(FsError_NotImplemented);
} }
R_SUCCEED(); 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 { Result GetTotalSpace(const char *path, s64 *out) override {
if (App::GetApp()->m_install_sd.Get()) { if (App::GetApp()->m_install_sd.Get()) {
return fs::FsNativeContentStorage(FsContentStorageId_SdCard).GetTotalSpace("/", out); return fs::FsNativeContentStorage(FsContentStorageId_SdCard).GetTotalSpace("/", out);
@@ -335,140 +446,113 @@ struct FsInstallProxy final : FsProxyBase {
return fs::FsNativeContentStorage(FsContentStorageId_User).GetFreeSpace("/", out); return fs::FsNativeContentStorage(FsContentStorageId_User).GetFreeSpace("/", out);
} }
} }
Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) override { Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) override {
if (FixPath(path) == "/") { R_TRY(FsProxyVfs::GetEntryType(path, out_entry_type));
*out_entry_type = FsDirEntryType_Dir; if (*out_entry_type == FsDirEntryType_File) {
R_SUCCEED(); R_TRY(FailedIfNotEnabled());
} else {
*out_entry_type = FsDirEntryType_File;
R_SUCCEED();
} }
R_SUCCEED();
} }
Result CreateFile(const char* path, s64 size, u32 option) override { Result CreateFile(const char* path, s64 size, u32 option) override {
return FailedIfNotEnabled(); R_TRY(FailedIfNotEnabled());
} R_TRY(IsValidFileType(path));
Result DeleteFile(const char* path) override { R_TRY(FsProxyVfs::CreateFile(path, size, option));
R_SUCCEED();
}
Result RenameFile(const char *old_path, const char *new_path) override {
R_SUCCEED(); R_SUCCEED();
} }
Result OpenFile(const char *path, u32 mode, FsFile *out_file) override { Result OpenFile(const char *path, u32 mode, FsFile *out_file) override {
if (mode & FsOpenMode_Read) { R_TRY(FailedIfNotEnabled());
R_SUCCEED(); R_TRY(IsValidFileType(path));
} else { R_TRY(FsProxyVfs::OpenFile(path, mode, out_file));
std::scoped_lock lock{g_shared_data.mutex}; log_write("[MTP] done file open: %s mode: 0x%X\n", path, mode);
if (!g_shared_data.enabled) {
R_THROW(0x1);
}
const char* ext = std::strrchr(path, '.'); if (mode & FsOpenMode_Write) {
if (!ext) { const auto& e = m_entries[out_file->s.object_id];
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);
}
// check if we already have this file queued. // check if we already have this file queued.
auto it = std::ranges::find(g_shared_data.queued_files, path); log_write("[MTP] checking if empty\n");
if (it != g_shared_data.queued_files.cend()) { R_UNLESS(g_shared_data.current_file.empty(), FsError_NotImplemented);
R_THROW(0x1); log_write("[MTP] is empty\n");
} g_shared_data.current_file = e.name;
g_shared_data.queued_files.push_back(path);
}
on_thing(); on_thing();
}
log_write("[MTP] got file: %s\n", path); log_write("[MTP] got file: %s\n", path);
R_SUCCEED(); 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 { 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) { 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)) { if (!g_shared_data.on_write || !g_shared_data.on_write(buf, write_size)) {
R_THROW(0x1); log_write("[MTP] failing as not written\n");
R_THROW(FsError_NotImplemented);
} }
R_TRY(FsProxyVfs::WriteFile(file, off, buf, write_size, option));
R_SUCCEED(); R_SUCCEED();
} }
void CloseFile(FsFile *file) override { void CloseFile(FsFile *file) override {
bool update{};
{ {
log_write("[MTP] closing file\n"); SCOPED_MUTEX(&g_shared_data.mutex);
std::scoped_lock lock{g_shared_data.mutex}; if (file->s.own_handle & FsOpenMode_Write) {
log_write("[MTP] closing valid file\n");
log_write("[MTP] closing current file\n"); log_write("[MTP] closing current file\n");
if (g_shared_data.on_close) { 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; g_shared_data.in_progress = false;
g_shared_data.queued_files.clear(); g_shared_data.current_file.clear();
update = true;
}
} }
if (update) {
on_thing(); on_thing();
std::memset(file, 0, sizeof(*file));
} }
Result CreateDirectory(const char* path) override { FsProxyVfs::CloseFile(file);
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));
} }
}; };
::haze::FsEntries g_fs_entries{}; ::haze::FsEntries g_fs_entries{};
void haze_callback(const ::haze::CallbackData *data) { 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(); App::NotifyFlashLed();
} }
} // namespace } // namespace
bool Init() { bool Init() {
std::scoped_lock lock{g_mutex}; SCOPED_MUTEX(&g_mutex);
if (g_is_running) { if (g_is_running) {
log_write("[MTP] already enabled, cannot open\n"); log_write("[MTP] already enabled, cannot open\n");
return false; return false;
@@ -481,7 +565,7 @@ bool Init() {
g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)")); g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)"));
g_should_exit = false; 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; return false;
} }
@@ -490,7 +574,7 @@ bool Init() {
} }
void Exit() { void Exit() {
std::scoped_lock lock{g_mutex}; SCOPED_MUTEX(&g_mutex);
if (!g_is_running) { if (!g_is_running) {
return; return;
} }
@@ -503,9 +587,8 @@ void Exit() {
log_write("[MTP] exitied\n"); log_write("[MTP] exitied\n");
} }
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) { void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
std::scoped_lock lock{g_shared_data.mutex}; SCOPED_MUTEX(&g_shared_data.mutex);
g_shared_data.user = user;
g_shared_data.on_start = on_start; g_shared_data.on_start = on_start;
g_shared_data.on_write = on_write; g_shared_data.on_write = on_write;
g_shared_data.on_close = on_close; g_shared_data.on_close = on_close;
@@ -513,7 +596,7 @@ void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_writ
} }
void DisableInstallMode() { void DisableInstallMode() {
std::scoped_lock lock{g_shared_data.mutex}; SCOPED_MUTEX(&g_shared_data.mutex);
g_shared_data.enabled = false; g_shared_data.enabled = false;
} }

View File

@@ -1,166 +1,25 @@
#include "ui/menus/ftp_menu.hpp" #include "ui/menus/ftp_menu.hpp"
#include "yati/yati.hpp"
#include "app.hpp" #include "app.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "log.hpp" #include "log.hpp"
#include "ui/nvg_util.hpp" #include "ui/nvg_util.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "ftpsrv_helper.hpp" #include "ftpsrv_helper.hpp"
#include <cstring>
#include <algorithm>
namespace sphaira::ui::menu::ftp { namespace sphaira::ui::menu::ftp {
namespace {
constexpr u64 MAX_BUFFER_SIZE = 1024*1024*32; Menu::Menu(u32 flags) : stream::Menu{"FTP Install"_i18n, flags} {
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);
m_was_ftp_enabled = App::GetFtpEnable(); m_was_ftp_enabled = App::GetFtpEnable();
if (!m_was_ftp_enabled) { if (!m_was_ftp_enabled) {
log_write("[FTP] wasn't enabled, forcefully enabling\n"); log_write("[FTP] wasn't enabled, forcefully enabling\n");
App::SetFtpEnable(true); 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_port = ftpsrv::GetPort();
m_anon = ftpsrv::IsAnon(); m_anon = ftpsrv::IsAnon();
@@ -173,71 +32,19 @@ Menu::Menu(u32 flags) : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n, flags} {
Menu::~Menu() { Menu::~Menu() {
// signal for thread to exit and wait. // signal for thread to exit and wait.
ftpsrv::DisableInstallMode(); ftpsrv::DisableInstallMode();
m_stop_source.request_stop();
if (m_source) {
m_source->Disable();
}
if (!m_was_ftp_enabled) { if (!m_was_ftp_enabled) {
log_write("[FTP] disabling on exit\n"); log_write("[FTP] disabling on exit\n");
App::SetFtpEnable(false); App::SetFtpEnable(false);
} }
App::SetAutoSleepDisabled(false);
log_write("closing data!!!!\n");
} }
void Menu::Update(Controller* controller, TouchInfo* touch) { void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch); stream::Menu::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;
}
} }
void Menu::Draw(NVGcontext* vg, Theme* theme) { void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); stream::Menu::Draw(vg, theme);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
const auto pdata = GetPolledData(); const auto pdata = GetPolledData();
if (pdata.ip) { if (pdata.ip) {
@@ -257,6 +64,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
float bounds[4]; float bounds[4];
nvgFontSize(vg, font_size); nvgFontSize(vg, font_size);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
// note: textbounds strips spaces...todo: use nvgTextGlyphPositions() instead. // note: textbounds strips spaces...todo: use nvgTextGlyphPositions() instead.
#define draw(key, ...) \ #define draw(key, ...) \
@@ -286,31 +94,10 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
#undef draw #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() { void Menu::OnDisableInstallMode() {
MenuBase::OnFocusGained(); ftpsrv::DisableInstallMode();
} }
} // namespace sphaira::ui::menu::ftp } // 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 "ui/menus/mtp_menu.hpp"
#include "yati/yati.hpp"
#include "usb/usbds.hpp" #include "usb/usbds.hpp"
#include "app.hpp" #include "app.hpp"
#include "defines.hpp" #include "defines.hpp"
@@ -7,16 +6,10 @@
#include "ui/nvg_util.hpp" #include "ui/nvg_util.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "haze_helper.hpp" #include "haze_helper.hpp"
#include <cstring>
#include <algorithm>
namespace sphaira::ui::menu::mtp { namespace sphaira::ui::menu::mtp {
namespace { namespace {
constexpr u64 MAX_BUFFER_SIZE = 1024*1024*32;
constexpr u64 SLEEPNS = 1000;
volatile bool IN_PUSH_THREAD{};
auto GetUsbStateStr(UsbState state) -> const char* { auto GetUsbStateStr(UsbState state) -> const char* {
switch (state) { switch (state) {
case UsbState_Detached: return "Detached"; case UsbState_Detached: return "Detached";
@@ -44,173 +37,34 @@ auto GetUsbSpeedStr(UsbDeviceSpeed speed) -> const char* {
return "Unknown"; 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 } // namespace
StreamFtp::StreamFtp(const fs::FsPath& path, std::stop_token token) { Menu::Menu(u32 flags) : stream::Menu{"MTP Install"_i18n, flags} {
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);
m_was_mtp_enabled = App::GetMtpEnable(); m_was_mtp_enabled = App::GetMtpEnable();
if (!m_was_mtp_enabled) { if (!m_was_mtp_enabled) {
log_write("[MTP] wasn't enabled, forcefully enabling\n"); log_write("[MTP] wasn't enabled, forcefully enabling\n");
App::SetMtpEnable(true); 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() { Menu::~Menu() {
// signal for thread to exit and wait. // signal for thread to exit and wait.
haze::DisableInstallMode(); haze::DisableInstallMode();
m_stop_source.request_stop();
if (m_source) {
m_source->Disable();
}
if (!m_was_mtp_enabled) { if (!m_was_mtp_enabled) {
log_write("[MTP] disabling on exit\n"); log_write("[MTP] disabling on exit\n");
App::SetMtpEnable(false); App::SetMtpEnable(false);
} }
App::SetAutoSleepDisabled(false);
log_write("closing data!!!!\n");
} }
void Menu::Update(Controller* controller, TouchInfo* touch) { void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch); stream::Menu::Update(controller, touch);
static TimeStamp poll_ts; static TimeStamp poll_ts;
if (poll_ts.GetSeconds() >= 1) { 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()); std::snprintf(buf, sizeof(buf), "State: %s | Speed: %s", i18n::get(GetUsbStateStr(state)).c_str(), i18n::get(GetUsbSpeedStr(speed)).c_str());
SetSubHeading(buf); SetSubHeading(buf);
} }
}
mutexLock(&m_mutex); void Menu::OnDisableInstallMode() {
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(); 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();
} }
} // namespace sphaira::ui::menu::mtp } // 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) { Result GetTicketData(std::span<const u8> ticket, es::TicketData* out) {
u64 data_off; u64 data_off;
R_TRY(GetTicketDataOffset(ticket, 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)); std::memcpy(out, ticket.data() + data_off, sizeof(*out));
// validate ticket data. // 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->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->title_key_type == es::TicketTitleKeyType_Common || out->title_key_type == es::TicketTitleKeyType_Personalized, Result_InvalidTicketKeyType);
R_UNLESS(out->master_key_revision <= 0x20, Result_InvalidTicketKeyRevision); R_UNLESS(out->master_key_revision <= 0x20, Result_InvalidTicketKeyRevision);
log_write("[ES] valid ticket data\n");
R_SUCCEED(); R_SUCCEED();
} }

View File

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