Files
sphaira/sphaira/source/ui/menus/usb_menu.cpp
ITotalJustice 4b06700187 add info boxes, fix case sensitive path compares, many other changes (see comments)
- nca::ParseControl now gets the nacp base on the language.
- xci returns a more descriptive error and logs info during install.
- replace volatile with atmoic, label atmoic methods with volatile as per the standard.
- s2s usb install skips verifying the content as its being installed from another switch.
- fix game / save menu bug where it would only load the nacp if it has started yet, now it will force load even if its in progress.
  this fixes save file zips having empty names.
- game menu saves the application type, to be later used for displaying if its a gamecard (inserted or not), launched etc.
2025-07-19 17:33:28 +01:00

190 lines
6.3 KiB
C++

#include "ui/menus/usb_menu.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::usb {
namespace {
constexpr u64 CONNECTION_TIMEOUT = UINT64_MAX;
constexpr u64 TRANSFER_TIMEOUT = UINT64_MAX;
constexpr u64 FINISHED_TIMEOUT = 1e+9 * 3; // 3 seconds.
void thread_func(void* user) {
auto app = static_cast<Menu*>(user);
for (;;) {
if (app->GetToken().stop_requested()) {
break;
}
const auto rc = app->m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
if (rc == Result_UsbCancelled) {
break;
}
// set connected status
mutexLock(&app->m_mutex);
if (R_SUCCEEDED(rc)) {
app->m_state = State::Connected_WaitForFileList;
} else {
app->m_state = State::None;
}
mutexUnlock(&app->m_mutex);
if (R_SUCCEEDED(rc)) {
std::vector<std::string> names;
if (R_SUCCEEDED(app->m_usb_source->WaitForConnection(CONNECTION_TIMEOUT, names))) {
mutexLock(&app->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&app->m_mutex));
app->m_state = State::Connected_StartingTransfer;
app->m_names = names;
break;
}
}
}
}
} // namespace
Menu::Menu(u32 flags) : MenuBase{"USB"_i18n, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});
SetAction(Button::X, Action{"Options"_i18n, [this](){
App::DisplayInstallOptions(false);
}});
// if mtp is enabled, disable it for now.
m_was_mtp_enabled = App::GetMtpEnable();
if (m_was_mtp_enabled) {
App::Notify("Disable MTP for usb install"_i18n);
App::SetMtpEnable(false);
}
// 3 second timeout for transfers.
m_usb_source = std::make_unique<yati::source::Usb>(TRANSFER_TIMEOUT);
if (R_FAILED(m_usb_source->GetOpenResult())) {
log_write("usb init open\n");
m_state = State::Failed;
}
mutexInit(&m_mutex);
if (m_state != State::Failed) {
threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, PRIO_PREEMPTIVE, 1);
threadStart(&m_thread);
}
}
Menu::~Menu() {
// signal for thread to exit and wait.
m_stop_source.request_stop();
m_usb_source->SignalCancel();
threadWaitForExit(&m_thread);
threadClose(&m_thread);
// free usb source before re-enabling mtp.
log_write("closing data!!!!\n");
m_usb_source.reset();
if (m_was_mtp_enabled) {
App::Notify("Re-enabled MTP"_i18n);
App::SetMtpEnable(true);
}
}
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
if (m_state == State::Connected_StartingTransfer) {
log_write("set to progress\n");
m_state = State::Progress;
log_write("got connection\n");
App::Push<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
ON_SCOPE_EXIT(m_usb_source->Finished(FINISHED_TIMEOUT));
// if we are doing s2s install, skip verifying the nca contents.
yati::ConfigOverride config_override{};
if (m_usb_source->IsStream()) {
config_override.skip_nca_hash_verify = true;
config_override.skip_rsa_header_fixed_key_verify = true;
config_override.skip_rsa_npdm_fixed_key_verify = true;
}
log_write("inside progress box\n");
for (const auto& file_name : m_names) {
pbox->SetTitle(file_name);
m_usb_source->SetFileNameForTranfser(file_name);
const auto rc = yati::InstallFromSource(pbox, m_usb_source.get(), file_name, config_override);
if (R_FAILED(rc)) {
m_usb_source->SignalCancel();
log_write("exiting usb install\n");
R_THROW(rc);
}
App::Notify("Installed via usb"_i18n);
}
R_SUCCEED();
}, [this](Result rc){
App::PushErrorBox(rc, "USB install failed!"_i18n);
if (R_SUCCEEDED(rc)) {
App::Notify("Usb install success!"_i18n);
m_state = State::Done;
SetPop();
} else {
m_state = State::Failed;
}
});
}
}
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:
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_WaitForFileList:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Connected, waiting for file list..."_i18n.c_str());
break;
case State::Connected_StartingTransfer:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Connected, starting transfer..."_i18n.c_str());
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 init usb, press B to exit..."_i18n.c_str());
break;
}
}
void Menu::OnFocusGained() {
MenuBase::OnFocusGained();
}
} // namespace sphaira::ui::menu::usb