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:
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!": "ページのロードエラー"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!": "페이지 로딩 오류!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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...": "Подключено, ожидание списка файлов...",
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!": "Помилка завантаження сторінки!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...": "已连接,正在等待文件列表...",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace sphaira::ftpsrv {
|
||||
bool Init();
|
||||
void Exit();
|
||||
|
||||
using OnInstallStart = std::function<bool(void* user, const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void(void* user)>;
|
||||
using OnInstallStart = std::function<bool(const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void()>;
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
|
||||
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
|
||||
void DisableInstallMode();
|
||||
|
||||
unsigned GetPort();
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace sphaira::haze {
|
||||
bool Init();
|
||||
void Exit();
|
||||
|
||||
using OnInstallStart = std::function<bool(void* user, const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void(void* user)>;
|
||||
using OnInstallStart = std::function<bool(const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void()>;
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
|
||||
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
|
||||
void DisableInstallMode();
|
||||
|
||||
} // namespace sphaira::haze
|
||||
|
||||
@@ -1,56 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
#include "ui/menus/install_stream_menu_base.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::ftp {
|
||||
|
||||
enum class State {
|
||||
// not connected.
|
||||
None,
|
||||
// just connected, starts the transfer.
|
||||
Connected,
|
||||
// set whilst transfer is in progress.
|
||||
Progress,
|
||||
// set when the transfer is finished.
|
||||
Done,
|
||||
// failed to connect.
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct StreamFtp final : yati::source::Stream {
|
||||
StreamFtp(const fs::FsPath& path, std::stop_token token);
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
|
||||
bool Push(const void* buf, s64 size);
|
||||
void Disable();
|
||||
|
||||
// private:
|
||||
fs::FsPath m_path{};
|
||||
std::stop_token m_token{};
|
||||
std::vector<u8> m_buffer{};
|
||||
Mutex m_mutex{};
|
||||
bool m_active{};
|
||||
// bool m_push_exit{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
struct Menu final : stream::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "FTP"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
// this should be private
|
||||
// private:
|
||||
std::shared_ptr<StreamFtp> m_source{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
// the below are shared across threads, lock with the above mutex!
|
||||
State m_state{State::None};
|
||||
void OnDisableInstallMode() override;
|
||||
|
||||
private:
|
||||
const char* m_user{};
|
||||
const char* m_pass{};
|
||||
unsigned m_port{};
|
||||
|
||||
62
sphaira/include/ui/menus/install_stream_menu_base.hpp
Normal file
62
sphaira/include/ui/menus/install_stream_menu_base.hpp
Normal 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
|
||||
@@ -1,56 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
#include "ui/menus/install_stream_menu_base.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::mtp {
|
||||
|
||||
enum class State {
|
||||
// not connected.
|
||||
None,
|
||||
// just connected, starts the transfer.
|
||||
Connected,
|
||||
// set whilst transfer is in progress.
|
||||
Progress,
|
||||
// set when the transfer is finished.
|
||||
Done,
|
||||
// failed to connect.
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct StreamFtp final : yati::source::Stream {
|
||||
StreamFtp(const fs::FsPath& path, std::stop_token token);
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
|
||||
bool Push(const void* buf, s64 size);
|
||||
void Disable();
|
||||
|
||||
// private:
|
||||
fs::FsPath m_path{};
|
||||
std::stop_token m_token{};
|
||||
std::vector<u8> m_buffer{};
|
||||
Mutex m_mutex{};
|
||||
bool m_active{};
|
||||
// bool m_push_exit{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
struct Menu final : stream::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "MTP"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
// this should be private
|
||||
// private:
|
||||
std::shared_ptr<StreamFtp> m_source{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
// the below are shared across threads, lock with the above mutex!
|
||||
State m_state{State::None};
|
||||
void OnDisableInstallMode() override;
|
||||
|
||||
private:
|
||||
bool m_was_mtp_enabled{};
|
||||
};
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "fs.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <minIni.h>
|
||||
#include <ftpsrv.h>
|
||||
@@ -16,8 +15,7 @@ namespace sphaira::ftpsrv {
|
||||
namespace {
|
||||
|
||||
struct InstallSharedData {
|
||||
std::mutex mutex;
|
||||
|
||||
Mutex mutex;
|
||||
std::deque<std::string> queued_files;
|
||||
|
||||
void* user;
|
||||
@@ -36,7 +34,7 @@ FtpSrvConfig g_ftpsrv_config = {0};
|
||||
volatile bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
Thread g_thread;
|
||||
std::mutex g_mutex{};
|
||||
Mutex g_mutex{};
|
||||
InstallSharedData g_shared_data{};
|
||||
|
||||
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
|
||||
@@ -59,13 +57,13 @@ struct VfsUserData {
|
||||
// ive given up with good names.
|
||||
void on_thing() {
|
||||
log_write("[FTP] doing on_thing\n");
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
log_write("[FTP] locked on_thing\n");
|
||||
|
||||
if (!g_shared_data.in_progress) {
|
||||
if (!g_shared_data.queued_files.empty()) {
|
||||
log_write("[FTP] pushing new file data\n");
|
||||
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.user, g_shared_data.queued_files[0].c_str())) {
|
||||
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.queued_files[0].c_str())) {
|
||||
g_shared_data.queued_files.clear();
|
||||
} else {
|
||||
log_write("[FTP] success on new file push\n");
|
||||
@@ -77,7 +75,7 @@ void on_thing() {
|
||||
|
||||
int vfs_install_open(void* user, const char* path, enum FtpVfsOpenMode mode) {
|
||||
{
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
data->valid = 0;
|
||||
|
||||
@@ -133,7 +131,7 @@ int vfs_install_read(void* user, void* buf, size_t size) {
|
||||
}
|
||||
|
||||
int vfs_install_write(void* user, const void* buf, size_t size) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
if (!g_shared_data.enabled) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
@@ -145,7 +143,7 @@ int vfs_install_write(void* user, const void* buf, size_t size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_shared_data.on_write || !g_shared_data.on_write(g_shared_data.user, buf, size)) {
|
||||
if (!g_shared_data.on_write || !g_shared_data.on_write(buf, size)) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
@@ -159,13 +157,13 @@ int vfs_install_seek(void* user, const void* buf, size_t size, size_t off) {
|
||||
}
|
||||
|
||||
int vfs_install_isfile_open(void* user) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
return data->valid;
|
||||
}
|
||||
|
||||
int vfs_install_isfile_ready(void* user) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
const auto ready = !g_shared_data.queued_files.empty() && data->path == g_shared_data.queued_files[0];
|
||||
return ready;
|
||||
@@ -174,7 +172,7 @@ int vfs_install_isfile_ready(void* user) {
|
||||
int vfs_install_close(void* user) {
|
||||
{
|
||||
log_write("[FTP] closing file\n");
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
if (data->valid) {
|
||||
log_write("[FTP] closing valid file\n");
|
||||
@@ -184,7 +182,7 @@ int vfs_install_close(void* user) {
|
||||
if (it == g_shared_data.queued_files.cbegin()) {
|
||||
log_write("[FTP] closing current file\n");
|
||||
if (g_shared_data.on_close) {
|
||||
g_shared_data.on_close(g_shared_data.user);
|
||||
g_shared_data.on_close();
|
||||
}
|
||||
|
||||
g_shared_data.in_progress = false;
|
||||
@@ -296,7 +294,7 @@ void loop(void* arg) {
|
||||
} // namespace
|
||||
|
||||
bool Init() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
if (g_is_running) {
|
||||
log_write("[FTP] already enabled, cannot open\n");
|
||||
return false;
|
||||
@@ -380,7 +378,7 @@ bool Init() {
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
if (!g_is_running) {
|
||||
return;
|
||||
}
|
||||
@@ -398,9 +396,8 @@ void Exit() {
|
||||
log_write("[FTP] exitied\n");
|
||||
}
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
g_shared_data.user = user;
|
||||
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
g_shared_data.on_start = on_start;
|
||||
g_shared_data.on_write = on_write;
|
||||
g_shared_data.on_close = on_close;
|
||||
@@ -408,27 +405,27 @@ void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_writ
|
||||
}
|
||||
|
||||
void DisableInstallMode() {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
g_shared_data.enabled = false;
|
||||
}
|
||||
|
||||
unsigned GetPort() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
return g_ftpsrv_config.port;
|
||||
}
|
||||
|
||||
bool IsAnon() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
return g_ftpsrv_config.anon;
|
||||
}
|
||||
|
||||
const char* GetUser() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
return g_ftpsrv_config.user;
|
||||
}
|
||||
|
||||
const char* GetPass() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
return g_ftpsrv_config.pass;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "evman.hpp"
|
||||
#include "i18n.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <haze.h>
|
||||
|
||||
@@ -14,8 +13,8 @@ namespace sphaira::haze {
|
||||
namespace {
|
||||
|
||||
struct InstallSharedData {
|
||||
std::mutex mutex;
|
||||
std::deque<std::string> queued_files;
|
||||
Mutex mutex;
|
||||
std::string current_file;
|
||||
|
||||
void* user;
|
||||
OnInstallStart on_start;
|
||||
@@ -30,7 +29,7 @@ constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
|
||||
constexpr int THREAD_CORE = 2;
|
||||
volatile bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
std::mutex g_mutex{};
|
||||
Mutex g_mutex{};
|
||||
InstallSharedData g_shared_data{};
|
||||
|
||||
const char* SUPPORTED_EXT[] = {
|
||||
@@ -40,14 +39,14 @@ const char* SUPPORTED_EXT[] = {
|
||||
// ive given up with good names.
|
||||
void on_thing() {
|
||||
log_write("[MTP] doing on_thing\n");
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
log_write("[MTP] locked on_thing\n");
|
||||
|
||||
if (!g_shared_data.in_progress) {
|
||||
if (!g_shared_data.queued_files.empty()) {
|
||||
if (!g_shared_data.current_file.empty()) {
|
||||
log_write("[MTP] pushing new file data\n");
|
||||
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.user, g_shared_data.queued_files[0].c_str())) {
|
||||
g_shared_data.queued_files.clear();
|
||||
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.current_file.c_str())) {
|
||||
g_shared_data.current_file.clear();
|
||||
} else {
|
||||
log_write("[MTP] success on new file push\n");
|
||||
g_shared_data.in_progress = true;
|
||||
@@ -71,7 +70,7 @@ struct FsProxyBase : ::haze::FileSystemProxyImpl {
|
||||
std::strcpy(buf, path);
|
||||
}
|
||||
|
||||
log_write("[FixPath] %s -> %s\n", path, buf.s);
|
||||
// log_write("[FixPath] %s -> %s\n", path, buf.s);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -233,8 +232,162 @@ private:
|
||||
std::shared_ptr<fs::Fs> m_fs{};
|
||||
};
|
||||
|
||||
struct FsDevNullProxy final : FsProxyBase {
|
||||
// fake fs that allows for files to create r/w on the root.
|
||||
// folders are not yet supported.
|
||||
struct FsProxyVfs : FsProxyBase {
|
||||
using FsProxyBase::FsProxyBase;
|
||||
virtual ~FsProxyVfs() = default;
|
||||
|
||||
auto GetFileName(const char* s) -> const char* {
|
||||
const auto file_name = std::strrchr(s, '/');
|
||||
if (!file_name || file_name[1] == '\0') {
|
||||
return nullptr;
|
||||
}
|
||||
return file_name + 1;
|
||||
}
|
||||
|
||||
virtual Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) {
|
||||
if (FixPath(path) == "/") {
|
||||
*out_entry_type = FsDirEntryType_Dir;
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
const auto file_name = GetFileName(path);
|
||||
R_UNLESS(file_name, FsError_PathNotFound);
|
||||
|
||||
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
|
||||
return !strcasecmp(file_name, e.name);
|
||||
});
|
||||
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
|
||||
|
||||
*out_entry_type = FsDirEntryType_File;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
virtual Result CreateFile(const char* path, s64 size, u32 option) {
|
||||
const auto file_name = GetFileName(path);
|
||||
R_UNLESS(file_name, FsError_PathNotFound);
|
||||
|
||||
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
|
||||
return !strcasecmp(file_name, e.name);
|
||||
});
|
||||
R_UNLESS(it == m_entries.end(), FsError_PathAlreadyExists);
|
||||
|
||||
FsDirectoryEntry entry{};
|
||||
std::strcpy(entry.name, file_name);
|
||||
entry.type = FsDirEntryType_File;
|
||||
entry.file_size = size;
|
||||
|
||||
m_entries.emplace_back(entry);
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual Result DeleteFile(const char* path) {
|
||||
const auto file_name = GetFileName(path);
|
||||
R_UNLESS(file_name, FsError_PathNotFound);
|
||||
|
||||
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
|
||||
return !strcasecmp(file_name, e.name);
|
||||
});
|
||||
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
|
||||
|
||||
m_entries.erase(it);
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual Result RenameFile(const char *old_path, const char *new_path) {
|
||||
const auto file_name = GetFileName(old_path);
|
||||
R_UNLESS(file_name, FsError_PathNotFound);
|
||||
|
||||
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
|
||||
return !strcasecmp(file_name, e.name);
|
||||
});
|
||||
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
|
||||
|
||||
const auto file_name_new = GetFileName(new_path);
|
||||
R_UNLESS(file_name_new, FsError_PathNotFound);
|
||||
|
||||
const auto new_it = std::ranges::find_if(m_entries, [file_name_new](auto& e){
|
||||
return !strcasecmp(file_name_new, e.name);
|
||||
});
|
||||
R_UNLESS(new_it == m_entries.end(), FsError_PathAlreadyExists);
|
||||
|
||||
std::strcpy(it->name, file_name_new);
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual Result OpenFile(const char *path, u32 mode, FsFile *out_file) {
|
||||
const auto file_name = GetFileName(path);
|
||||
R_UNLESS(file_name, FsError_PathNotFound);
|
||||
|
||||
const auto it = std::ranges::find_if(m_entries, [file_name](auto& e){
|
||||
return !strcasecmp(file_name, e.name);
|
||||
});
|
||||
R_UNLESS(it != m_entries.end(), FsError_PathNotFound);
|
||||
|
||||
out_file->s.object_id = std::distance(m_entries.begin(), it);
|
||||
out_file->s.own_handle = mode;
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual Result GetFileSize(FsFile *file, s64 *out_size) {
|
||||
auto& e = m_entries[file->s.object_id];
|
||||
*out_size = e.file_size;
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual Result SetFileSize(FsFile *file, s64 size) {
|
||||
auto& e = m_entries[file->s.object_id];
|
||||
e.file_size = size;
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) {
|
||||
// stub for now as it may confuse users who think that the returned file is valid.
|
||||
// the code below can be used to benchmark mtp reads.
|
||||
R_THROW(FsError_NotImplemented);
|
||||
// auto& e = m_entries[file->s.object_id];
|
||||
// read_size = std::min<s64>(e.file_size - off, read_size);
|
||||
// std::memset(buf, 0, read_size);
|
||||
// *out_bytes_read = read_size;
|
||||
// R_SUCCEED();
|
||||
}
|
||||
virtual Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) {
|
||||
auto& e = m_entries[file->s.object_id];
|
||||
e.file_size = std::max<s64>(e.file_size, off + write_size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
virtual void CloseFile(FsFile *file) {
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
}
|
||||
|
||||
Result CreateDirectory(const char* path) override {
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
Result DeleteDirectoryRecursively(const char* path) override {
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
Result RenameDirectory(const char *old_path, const char *new_path) override {
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) override {
|
||||
std::memset(out_dir, 0, sizeof(*out_dir));
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) override {
|
||||
max_entries = std::min<s64>(m_entries.size()- d->s.object_id, max_entries);
|
||||
std::memcpy(buf, m_entries.data() + d->s.object_id, max_entries * sizeof(*buf));
|
||||
d->s.object_id += max_entries;
|
||||
*out_total_entries = max_entries;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) override {
|
||||
*out_count = m_entries.size();
|
||||
R_SUCCEED();
|
||||
}
|
||||
void CloseDirectory(FsDir *d) override {
|
||||
std::memset(d, 0, sizeof(*d));
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<FsDirectoryEntry> m_entries;
|
||||
};
|
||||
|
||||
struct FsDevNullProxy final : FsProxyVfs {
|
||||
using FsProxyVfs::FsProxyVfs;
|
||||
|
||||
Result GetTotalSpace(const char *path, s64 *out) override {
|
||||
*out = 1024ULL * 1024ULL * 1024ULL * 256ULL;
|
||||
@@ -244,83 +397,41 @@ struct FsDevNullProxy final : FsProxyBase {
|
||||
*out = 1024ULL * 1024ULL * 1024ULL * 256ULL;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) override {
|
||||
if (FixPath(path) == "/") {
|
||||
*out_entry_type = FsDirEntryType_Dir;
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
*out_entry_type = FsDirEntryType_File;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
Result CreateFile(const char* path, s64 size, u32 option) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result DeleteFile(const char* path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result RenameFile(const char *old_path, const char *new_path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result OpenFile(const char *path, u32 mode, FsFile *out_file) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result GetFileSize(FsFile *file, s64 *out_size) override {
|
||||
*out_size = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result SetFileSize(FsFile *file, s64 size) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) override {
|
||||
*out_bytes_read = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
void CloseFile(FsFile *file) override {
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
}
|
||||
|
||||
Result CreateDirectory(const char* path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result DeleteDirectoryRecursively(const char* path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result RenameDirectory(const char *old_path, const char *new_path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) override {
|
||||
*out_total_entries = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) override {
|
||||
*out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
void CloseDirectory(FsDir *d) override {
|
||||
std::memset(d, 0, sizeof(*d));
|
||||
}
|
||||
};
|
||||
|
||||
struct FsInstallProxy final : FsProxyBase {
|
||||
using FsProxyBase::FsProxyBase;
|
||||
struct FsInstallProxy final : FsProxyVfs {
|
||||
using FsProxyVfs::FsProxyVfs;
|
||||
|
||||
Result FailedIfNotEnabled() {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
if (!g_shared_data.enabled) {
|
||||
App::Notify("Please launch MTP install menu before trying to install"_i18n);
|
||||
R_THROW(0x1);
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// TODO: impl this.
|
||||
Result IsValidFileType(const char* name) {
|
||||
const char* ext = std::strrchr(name, '.');
|
||||
if (!ext) {
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < std::size(SUPPORTED_EXT); i++) {
|
||||
if (!strcasecmp(ext, SUPPORTED_EXT[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetTotalSpace(const char *path, s64 *out) override {
|
||||
if (App::GetApp()->m_install_sd.Get()) {
|
||||
return fs::FsNativeContentStorage(FsContentStorageId_SdCard).GetTotalSpace("/", out);
|
||||
@@ -335,140 +446,113 @@ struct FsInstallProxy final : FsProxyBase {
|
||||
return fs::FsNativeContentStorage(FsContentStorageId_User).GetFreeSpace("/", out);
|
||||
}
|
||||
}
|
||||
|
||||
Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) override {
|
||||
if (FixPath(path) == "/") {
|
||||
*out_entry_type = FsDirEntryType_Dir;
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
*out_entry_type = FsDirEntryType_File;
|
||||
R_SUCCEED();
|
||||
R_TRY(FsProxyVfs::GetEntryType(path, out_entry_type));
|
||||
if (*out_entry_type == FsDirEntryType_File) {
|
||||
R_TRY(FailedIfNotEnabled());
|
||||
}
|
||||
}
|
||||
Result CreateFile(const char* path, s64 size, u32 option) override {
|
||||
return FailedIfNotEnabled();
|
||||
}
|
||||
Result DeleteFile(const char* path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result RenameFile(const char *old_path, const char *new_path) override {
|
||||
Result CreateFile(const char* path, s64 size, u32 option) override {
|
||||
R_TRY(FailedIfNotEnabled());
|
||||
R_TRY(IsValidFileType(path));
|
||||
R_TRY(FsProxyVfs::CreateFile(path, size, option));
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result OpenFile(const char *path, u32 mode, FsFile *out_file) override {
|
||||
if (mode & FsOpenMode_Read) {
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
if (!g_shared_data.enabled) {
|
||||
R_THROW(0x1);
|
||||
}
|
||||
R_TRY(FailedIfNotEnabled());
|
||||
R_TRY(IsValidFileType(path));
|
||||
R_TRY(FsProxyVfs::OpenFile(path, mode, out_file));
|
||||
log_write("[MTP] done file open: %s mode: 0x%X\n", path, mode);
|
||||
|
||||
const char* ext = std::strrchr(path, '.');
|
||||
if (!ext) {
|
||||
R_THROW(0x1);
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < std::size(SUPPORTED_EXT); i++) {
|
||||
if (!strcasecmp(ext, SUPPORTED_EXT[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
R_THROW(0x1);
|
||||
}
|
||||
if (mode & FsOpenMode_Write) {
|
||||
const auto& e = m_entries[out_file->s.object_id];
|
||||
|
||||
// check if we already have this file queued.
|
||||
auto it = std::ranges::find(g_shared_data.queued_files, path);
|
||||
if (it != g_shared_data.queued_files.cend()) {
|
||||
R_THROW(0x1);
|
||||
}
|
||||
|
||||
g_shared_data.queued_files.push_back(path);
|
||||
log_write("[MTP] checking if empty\n");
|
||||
R_UNLESS(g_shared_data.current_file.empty(), FsError_NotImplemented);
|
||||
log_write("[MTP] is empty\n");
|
||||
g_shared_data.current_file = e.name;
|
||||
on_thing();
|
||||
}
|
||||
|
||||
on_thing();
|
||||
log_write("[MTP] got file: %s\n", path);
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result GetFileSize(FsFile *file, s64 *out_size) override {
|
||||
*out_size = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result SetFileSize(FsFile *file, s64 size) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) override {
|
||||
*out_bytes_read = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) override {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
if (!g_shared_data.enabled) {
|
||||
R_THROW(0x1);
|
||||
log_write("[MTP] failing as not enabled\n");
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
|
||||
if (!g_shared_data.on_write || !g_shared_data.on_write(g_shared_data.user, buf, write_size)) {
|
||||
R_THROW(0x1);
|
||||
if (!g_shared_data.on_write || !g_shared_data.on_write(buf, write_size)) {
|
||||
log_write("[MTP] failing as not written\n");
|
||||
R_THROW(FsError_NotImplemented);
|
||||
}
|
||||
|
||||
R_TRY(FsProxyVfs::WriteFile(file, off, buf, write_size, option));
|
||||
R_SUCCEED();
|
||||
}
|
||||
void CloseFile(FsFile *file) override {
|
||||
bool update{};
|
||||
{
|
||||
log_write("[MTP] closing file\n");
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
log_write("[MTP] closing valid file\n");
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
if (file->s.own_handle & FsOpenMode_Write) {
|
||||
log_write("[MTP] closing current file\n");
|
||||
if (g_shared_data.on_close) {
|
||||
g_shared_data.on_close();
|
||||
}
|
||||
|
||||
log_write("[MTP] closing current file\n");
|
||||
if (g_shared_data.on_close) {
|
||||
g_shared_data.on_close(g_shared_data.user);
|
||||
g_shared_data.in_progress = false;
|
||||
g_shared_data.current_file.clear();
|
||||
update = true;
|
||||
}
|
||||
|
||||
g_shared_data.in_progress = false;
|
||||
g_shared_data.queued_files.clear();
|
||||
}
|
||||
|
||||
on_thing();
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
}
|
||||
if (update) {
|
||||
on_thing();
|
||||
}
|
||||
|
||||
Result CreateDirectory(const char* path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result DeleteDirectoryRecursively(const char* path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result RenameDirectory(const char *old_path, const char *new_path) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) override {
|
||||
*out_total_entries = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) override {
|
||||
*out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
void CloseDirectory(FsDir *d) override {
|
||||
std::memset(d, 0, sizeof(*d));
|
||||
FsProxyVfs::CloseFile(file);
|
||||
}
|
||||
};
|
||||
|
||||
::haze::FsEntries g_fs_entries{};
|
||||
|
||||
void haze_callback(const ::haze::CallbackData *data) {
|
||||
auto& e = *data;
|
||||
|
||||
switch (e.type) {
|
||||
case ::haze::CallbackType_OpenSession: log_write("[LIBHAZE] Opening Session\n"); break;
|
||||
case ::haze::CallbackType_CloseSession: log_write("[LIBHAZE] Closing Session\n"); break;
|
||||
|
||||
case ::haze::CallbackType_CreateFile: log_write("[LIBHAZE] Creating File: %s\n", e.file.filename); break;
|
||||
case ::haze::CallbackType_DeleteFile: log_write("[LIBHAZE] Deleting File: %s\n", e.file.filename); break;
|
||||
|
||||
case ::haze::CallbackType_RenameFile: log_write("[LIBHAZE] Rename File: %s -> %s\n", e.rename.filename, e.rename.newname); break;
|
||||
case ::haze::CallbackType_RenameFolder: log_write("[LIBHAZE] Rename Folder: %s -> %s\n", e.rename.filename, e.rename.newname); break;
|
||||
|
||||
case ::haze::CallbackType_CreateFolder: log_write("[LIBHAZE] Creating Folder: %s\n", e.file.filename); break;
|
||||
case ::haze::CallbackType_DeleteFolder: log_write("[LIBHAZE] Deleting Folder: %s\n", e.file.filename); break;
|
||||
|
||||
case ::haze::CallbackType_ReadBegin: log_write("[LIBHAZE] Reading File Begin: %s \n", e.file.filename); break;
|
||||
case ::haze::CallbackType_ReadProgress: log_write("\t[LIBHAZE] Reading File: offset: %lld size: %lld\n", e.progress.offset, e.progress.size); break;
|
||||
case ::haze::CallbackType_ReadEnd: log_write("[LIBHAZE] Reading File Finished: %s\n", e.file.filename); break;
|
||||
|
||||
case ::haze::CallbackType_WriteBegin: log_write("[LIBHAZE] Writing File Begin: %s \n", e.file.filename); break;
|
||||
case ::haze::CallbackType_WriteProgress: log_write("\t[LIBHAZE] Writing File: offset: %lld size: %lld\n", e.progress.offset, e.progress.size); break;
|
||||
case ::haze::CallbackType_WriteEnd: log_write("[LIBHAZE] Writing File Finished: %s\n", e.file.filename); break;
|
||||
}
|
||||
|
||||
App::NotifyFlashLed();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Init() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
if (g_is_running) {
|
||||
log_write("[MTP] already enabled, cannot open\n");
|
||||
return false;
|
||||
@@ -481,7 +565,7 @@ bool Init() {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)"));
|
||||
|
||||
g_should_exit = false;
|
||||
if (!::haze::Initialize(haze_callback, PRIO_PREEMPTIVE, 2, g_fs_entries)) {
|
||||
if (!::haze::Initialize(haze_callback, THREAD_PRIO, THREAD_CORE, g_fs_entries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -490,7 +574,7 @@ bool Init() {
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
if (!g_is_running) {
|
||||
return;
|
||||
}
|
||||
@@ -503,9 +587,8 @@ void Exit() {
|
||||
log_write("[MTP] exitied\n");
|
||||
}
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
g_shared_data.user = user;
|
||||
void InitInstallMode(OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
g_shared_data.on_start = on_start;
|
||||
g_shared_data.on_write = on_write;
|
||||
g_shared_data.on_close = on_close;
|
||||
@@ -513,7 +596,7 @@ void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_writ
|
||||
}
|
||||
|
||||
void DisableInstallMode() {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
SCOPED_MUTEX(&g_shared_data.mutex);
|
||||
g_shared_data.enabled = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,166 +1,25 @@
|
||||
#include "ui/menus/ftp_menu.hpp"
|
||||
#include "yati/yati.hpp"
|
||||
#include "app.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "ftpsrv_helper.hpp"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
namespace sphaira::ui::menu::ftp {
|
||||
namespace {
|
||||
|
||||
constexpr u64 MAX_BUFFER_SIZE = 1024*1024*32;
|
||||
constexpr u64 SLEEPNS = 1000;
|
||||
volatile bool IN_PUSH_THREAD{};
|
||||
|
||||
bool OnInstallStart(void* user, const char* path) {
|
||||
auto menu = (Menu*)user;
|
||||
log_write("[INSTALL] inside OnInstallStart()\n");
|
||||
|
||||
for (;;) {
|
||||
mutexLock(&menu->m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
|
||||
|
||||
if (menu->m_state != State::Progress) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (menu->GetToken().stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
svcSleepThread(1e+6);
|
||||
}
|
||||
|
||||
log_write("[INSTALL] OnInstallStart() got state: %u\n", (u8)menu->m_state);
|
||||
|
||||
if (menu->m_source) {
|
||||
log_write("[INSTALL] OnInstallStart() we have source\n");
|
||||
for (;;) {
|
||||
mutexLock(&menu->m_source->m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&menu->m_source->m_mutex));
|
||||
|
||||
if (!IN_PUSH_THREAD) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (menu->GetToken().stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
svcSleepThread(1e+6);
|
||||
}
|
||||
|
||||
log_write("[INSTALL] OnInstallStart() stopped polling source\n");
|
||||
}
|
||||
|
||||
log_write("[INSTALL] OnInstallStart() doing make_shared\n");
|
||||
menu->m_source = std::make_shared<StreamFtp>(path, menu->GetToken());
|
||||
|
||||
mutexLock(&menu->m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
|
||||
menu->m_state = State::Connected;
|
||||
log_write("[INSTALL] OnInstallStart() done make shared\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnInstallWrite(void* user, const void* buf, size_t size) {
|
||||
auto menu = (Menu*)user;
|
||||
|
||||
return menu->m_source->Push(buf, size);
|
||||
}
|
||||
|
||||
void OnInstallClose(void* user) {
|
||||
auto menu = (Menu*)user;
|
||||
menu->m_source->Disable();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StreamFtp::StreamFtp(const fs::FsPath& path, std::stop_token token) {
|
||||
m_path = path;
|
||||
m_token = token;
|
||||
m_buffer.reserve(MAX_BUFFER_SIZE);
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
Result StreamFtp::ReadChunk(void* buf, s64 size, u64* bytes_read) {
|
||||
while (!m_token.stop_requested()) {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (m_buffer.empty()) {
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
|
||||
svcSleepThread(SLEEPNS);
|
||||
} else {
|
||||
size = std::min<s64>(size, m_buffer.size());
|
||||
std::memcpy(buf, m_buffer.data(), size);
|
||||
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + size);
|
||||
*bytes_read = size;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
return 0x1;
|
||||
}
|
||||
|
||||
bool StreamFtp::Push(const void* buf, s64 size) {
|
||||
IN_PUSH_THREAD = true;
|
||||
ON_SCOPE_EXIT(IN_PUSH_THREAD = false);
|
||||
|
||||
while (!m_token.stop_requested()) {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_buffer.size() + size >= MAX_BUFFER_SIZE) {
|
||||
svcSleepThread(SLEEPNS);
|
||||
} else {
|
||||
const auto offset = m_buffer.size();
|
||||
m_buffer.resize(offset + size);
|
||||
std::memcpy(m_buffer.data() + offset, buf, size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void StreamFtp::Disable() {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
m_active = false;
|
||||
}
|
||||
|
||||
Menu::Menu(u32 flags) : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n, flags} {
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}});
|
||||
|
||||
SetAction(Button::X, Action{"Options"_i18n, [this](){
|
||||
App::DisplayInstallOptions(false);
|
||||
}});
|
||||
|
||||
App::SetAutoSleepDisabled(true);
|
||||
|
||||
mutexInit(&m_mutex);
|
||||
Menu::Menu(u32 flags) : stream::Menu{"FTP Install"_i18n, flags} {
|
||||
m_was_ftp_enabled = App::GetFtpEnable();
|
||||
if (!m_was_ftp_enabled) {
|
||||
log_write("[FTP] wasn't enabled, forcefully enabling\n");
|
||||
App::SetFtpEnable(true);
|
||||
}
|
||||
|
||||
ftpsrv::InitInstallMode(this, OnInstallStart, OnInstallWrite, OnInstallClose);
|
||||
ftpsrv::InitInstallMode(
|
||||
[this](const char* path){ return OnInstallStart(path); },
|
||||
[this](const void *buf, size_t size){ return OnInstallWrite(buf, size); },
|
||||
[this](){ return OnInstallClose(); }
|
||||
);
|
||||
|
||||
m_port = ftpsrv::GetPort();
|
||||
m_anon = ftpsrv::IsAnon();
|
||||
@@ -173,71 +32,19 @@ Menu::Menu(u32 flags) : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n, flags} {
|
||||
Menu::~Menu() {
|
||||
// signal for thread to exit and wait.
|
||||
ftpsrv::DisableInstallMode();
|
||||
m_stop_source.request_stop();
|
||||
|
||||
if (m_source) {
|
||||
m_source->Disable();
|
||||
}
|
||||
|
||||
if (!m_was_ftp_enabled) {
|
||||
log_write("[FTP] disabling on exit\n");
|
||||
App::SetFtpEnable(false);
|
||||
}
|
||||
|
||||
App::SetAutoSleepDisabled(false);
|
||||
log_write("closing data!!!!\n");
|
||||
}
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
switch (m_state) {
|
||||
case State::None:
|
||||
break;
|
||||
|
||||
case State::Connected:
|
||||
log_write("set to progress\n");
|
||||
m_state = State::Progress;
|
||||
log_write("got connection\n");
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
|
||||
log_write("inside progress box\n");
|
||||
const auto rc = yati::InstallFromSource(pbox, m_source, m_source->m_path);
|
||||
if (R_FAILED(rc)) {
|
||||
m_source->Disable();
|
||||
R_THROW(rc);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}, [this](Result rc){
|
||||
App::PushErrorBox(rc, "Ftp install failed!"_i18n);
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
App::Notify("Ftp install success!"_i18n);
|
||||
m_state = State::Done;
|
||||
} else {
|
||||
m_state = State::Failed;
|
||||
}
|
||||
}));
|
||||
break;
|
||||
|
||||
case State::Progress:
|
||||
case State::Done:
|
||||
case State::Failed:
|
||||
break;
|
||||
}
|
||||
stream::Menu::Update(controller, touch);
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
MenuBase::Draw(vg, theme);
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
stream::Menu::Draw(vg, theme);
|
||||
|
||||
const auto pdata = GetPolledData();
|
||||
if (pdata.ip) {
|
||||
@@ -257,6 +64,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
float bounds[4];
|
||||
|
||||
nvgFontSize(vg, font_size);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
|
||||
// note: textbounds strips spaces...todo: use nvgTextGlyphPositions() instead.
|
||||
#define draw(key, ...) \
|
||||
@@ -286,31 +94,10 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
#undef draw
|
||||
|
||||
switch (m_state) {
|
||||
case State::None:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Waiting for connection..."_i18n.c_str());
|
||||
break;
|
||||
|
||||
case State::Connected:
|
||||
break;
|
||||
|
||||
case State::Progress:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Transferring data..."_i18n.c_str());
|
||||
break;
|
||||
|
||||
case State::Done:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Press B to exit..."_i18n.c_str());
|
||||
break;
|
||||
|
||||
case State::Failed:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to install via FTP, press B to exit..."_i18n.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
MenuBase::OnFocusGained();
|
||||
void Menu::OnDisableInstallMode() {
|
||||
ftpsrv::DisableInstallMode();
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::ftp
|
||||
|
||||
225
sphaira/source/ui/menus/install_stream_menu_base.cpp
Normal file
225
sphaira/source/ui/menus/install_stream_menu_base.cpp
Normal 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
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "ui/menus/mtp_menu.hpp"
|
||||
#include "yati/yati.hpp"
|
||||
#include "usb/usbds.hpp"
|
||||
#include "app.hpp"
|
||||
#include "defines.hpp"
|
||||
@@ -7,16 +6,10 @@
|
||||
#include "ui/nvg_util.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "haze_helper.hpp"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
namespace sphaira::ui::menu::mtp {
|
||||
namespace {
|
||||
|
||||
constexpr u64 MAX_BUFFER_SIZE = 1024*1024*32;
|
||||
constexpr u64 SLEEPNS = 1000;
|
||||
volatile bool IN_PUSH_THREAD{};
|
||||
|
||||
auto GetUsbStateStr(UsbState state) -> const char* {
|
||||
switch (state) {
|
||||
case UsbState_Detached: return "Detached";
|
||||
@@ -44,173 +37,34 @@ auto GetUsbSpeedStr(UsbDeviceSpeed speed) -> const char* {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
bool OnInstallStart(void* user, const char* path) {
|
||||
auto menu = (Menu*)user;
|
||||
log_write("[INSTALL] inside OnInstallStart()\n");
|
||||
|
||||
for (;;) {
|
||||
mutexLock(&menu->m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
|
||||
|
||||
if (menu->m_state != State::Progress) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (menu->GetToken().stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
svcSleepThread(1e+6);
|
||||
}
|
||||
|
||||
log_write("[INSTALL] OnInstallStart() got state: %u\n", (u8)menu->m_state);
|
||||
|
||||
if (menu->m_source) {
|
||||
log_write("[INSTALL] OnInstallStart() we have source\n");
|
||||
for (;;) {
|
||||
mutexLock(&menu->m_source->m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&menu->m_source->m_mutex));
|
||||
|
||||
if (!IN_PUSH_THREAD) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (menu->GetToken().stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
svcSleepThread(1e+6);
|
||||
}
|
||||
|
||||
log_write("[INSTALL] OnInstallStart() stopped polling source\n");
|
||||
}
|
||||
|
||||
log_write("[INSTALL] OnInstallStart() doing make_shared\n");
|
||||
menu->m_source = std::make_shared<StreamFtp>(path, menu->GetToken());
|
||||
|
||||
mutexLock(&menu->m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&menu->m_mutex));
|
||||
menu->m_state = State::Connected;
|
||||
log_write("[INSTALL] OnInstallStart() done make shared\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnInstallWrite(void* user, const void* buf, size_t size) {
|
||||
auto menu = (Menu*)user;
|
||||
|
||||
return menu->m_source->Push(buf, size);
|
||||
}
|
||||
|
||||
void OnInstallClose(void* user) {
|
||||
auto menu = (Menu*)user;
|
||||
menu->m_source->Disable();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StreamFtp::StreamFtp(const fs::FsPath& path, std::stop_token token) {
|
||||
m_path = path;
|
||||
m_token = token;
|
||||
m_buffer.reserve(MAX_BUFFER_SIZE);
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
Result StreamFtp::ReadChunk(void* buf, s64 size, u64* bytes_read) {
|
||||
while (!m_token.stop_requested()) {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (m_buffer.empty()) {
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
|
||||
svcSleepThread(SLEEPNS);
|
||||
} else {
|
||||
size = std::min<s64>(size, m_buffer.size());
|
||||
std::memcpy(buf, m_buffer.data(), size);
|
||||
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + size);
|
||||
*bytes_read = size;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
return 0x1;
|
||||
}
|
||||
|
||||
bool StreamFtp::Push(const void* buf, s64 size) {
|
||||
IN_PUSH_THREAD = true;
|
||||
ON_SCOPE_EXIT(IN_PUSH_THREAD = false);
|
||||
|
||||
while (!m_token.stop_requested()) {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_buffer.size() + size >= MAX_BUFFER_SIZE) {
|
||||
svcSleepThread(SLEEPNS);
|
||||
} else {
|
||||
const auto offset = m_buffer.size();
|
||||
m_buffer.resize(offset + size);
|
||||
std::memcpy(m_buffer.data() + offset, buf, size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void StreamFtp::Disable() {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
m_active = false;
|
||||
}
|
||||
|
||||
Menu::Menu(u32 flags) : MenuBase{"MTP Install"_i18n, flags} {
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}});
|
||||
|
||||
SetAction(Button::X, Action{"Options"_i18n, [this](){
|
||||
App::DisplayInstallOptions(false);
|
||||
}});
|
||||
|
||||
App::SetAutoSleepDisabled(true);
|
||||
|
||||
mutexInit(&m_mutex);
|
||||
Menu::Menu(u32 flags) : stream::Menu{"MTP Install"_i18n, flags} {
|
||||
m_was_mtp_enabled = App::GetMtpEnable();
|
||||
if (!m_was_mtp_enabled) {
|
||||
log_write("[MTP] wasn't enabled, forcefully enabling\n");
|
||||
App::SetMtpEnable(true);
|
||||
}
|
||||
|
||||
haze::InitInstallMode(this, OnInstallStart, OnInstallWrite, OnInstallClose);
|
||||
haze::InitInstallMode(
|
||||
[this](const char* path){ return OnInstallStart(path); },
|
||||
[this](const void *buf, size_t size){ return OnInstallWrite(buf, size); },
|
||||
[this](){ return OnInstallClose(); }
|
||||
);
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
// signal for thread to exit and wait.
|
||||
haze::DisableInstallMode();
|
||||
m_stop_source.request_stop();
|
||||
|
||||
if (m_source) {
|
||||
m_source->Disable();
|
||||
}
|
||||
|
||||
if (!m_was_mtp_enabled) {
|
||||
log_write("[MTP] disabling on exit\n");
|
||||
App::SetMtpEnable(false);
|
||||
}
|
||||
|
||||
App::SetAutoSleepDisabled(false);
|
||||
log_write("closing data!!!!\n");
|
||||
}
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
stream::Menu::Update(controller, touch);
|
||||
|
||||
static TimeStamp poll_ts;
|
||||
if (poll_ts.GetSeconds() >= 1) {
|
||||
@@ -226,65 +80,10 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
std::snprintf(buf, sizeof(buf), "State: %s | Speed: %s", i18n::get(GetUsbStateStr(state)).c_str(), i18n::get(GetUsbSpeedStr(speed)).c_str());
|
||||
SetSubHeading(buf);
|
||||
}
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (m_state == State::Connected) {
|
||||
log_write("set to progress\n");
|
||||
m_state = State::Progress;
|
||||
log_write("got connection\n");
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
|
||||
log_write("inside progress box\n");
|
||||
const auto rc = yati::InstallFromSource(pbox, m_source, m_source->m_path);
|
||||
if (R_FAILED(rc)) {
|
||||
m_source->Disable();
|
||||
R_THROW(rc);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}, [this](Result rc){
|
||||
App::PushErrorBox(rc, "MTP install failed!"_i18n);
|
||||
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
App::Notify("MTP install success!"_i18n);
|
||||
m_state = State::Done;
|
||||
} else {
|
||||
m_state = State::Failed;
|
||||
haze::DisableInstallMode();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
MenuBase::Draw(vg, theme);
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
switch (m_state) {
|
||||
case State::None:
|
||||
case State::Done:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder on PC"_i18n.c_str());
|
||||
break;
|
||||
|
||||
case State::Connected:
|
||||
case State::Progress:
|
||||
break;
|
||||
|
||||
case State::Failed:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to install via MTP, press B to exit..."_i18n.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
MenuBase::OnFocusGained();
|
||||
void Menu::OnDisableInstallMode() {
|
||||
haze::DisableInstallMode();
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::mtp
|
||||
|
||||
@@ -166,12 +166,19 @@ Result GetTicketDataOffset(std::span<const u8> ticket, u64& out) {
|
||||
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out) {
|
||||
u64 data_off;
|
||||
R_TRY(GetTicketDataOffset(ticket, data_off));
|
||||
if (ticket.size() < data_off + sizeof(*out)) {
|
||||
log_write("[ES] invalid ticket size: %zu vs %zu\n", ticket.size(), data_off + sizeof(*out));
|
||||
R_THROW(Result_EsBadTicketSize);
|
||||
}
|
||||
|
||||
std::memcpy(out, ticket.data() + data_off, sizeof(*out));
|
||||
|
||||
// validate ticket data.
|
||||
log_write("[ES] validating ticket data\n");
|
||||
R_UNLESS(out->ticket_version1 == 0x2, Result_InvalidTicketVersion); // must be version 2.
|
||||
R_UNLESS(out->title_key_type == es::TicketTitleKeyType_Common || out->title_key_type == es::TicketTitleKeyType_Personalized, Result_InvalidTicketKeyType);
|
||||
R_UNLESS(out->master_key_revision <= 0x20, Result_InvalidTicketKeyRevision);
|
||||
log_write("[ES] valid ticket data\n");
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -307,8 +307,10 @@ void ThreadData::WakeAllThreads() {
|
||||
Result ThreadData::Read(void* buf, s64 size, u64* bytes_read) {
|
||||
size = std::min<s64>(size, nca->size - read_offset);
|
||||
const auto rc = yati->source->Read(buf, nca->offset + read_offset, size, bytes_read);
|
||||
read_offset += *bytes_read;
|
||||
R_TRY(rc);
|
||||
|
||||
R_UNLESS(size == *bytes_read, Result_YatiInvalidNcaReadSize);
|
||||
read_offset += *bytes_read;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user