Files
sphaira/sphaira/source/ui/menus/install_stream_menu_base.cpp
ITotalJustice 3f99afaa38 add personalised -> common convert. patch bad common tickets. fix yati installing nca's if already installed.
- dumped nsp now have the tik/cert at the end of the file table, rather than the beginning.
- dumped nsp patches the ticket if needed (no personalised dumping yet).
- installing titles will now patch the ticket, performing personalised -> common convert if needed, as well as fixing bad common tickets.
- yati no longer tries to install ncas if they already exist.
- ticket only option now actually works.
- fixed some translations.
- removed unused error codes.
2025-06-18 15:07:07 +01:00

262 lines
7.1 KiB
C++

#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.
log_write("[Stream::Push] buffer is full, delaying\n");
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");
for (;;) {
{
SCOPED_MUTEX(&m_mutex);
if (m_state != State::Progress) {
break;
}
if (GetToken().stop_requested()) {
return false;
}
}
svcSleepThread(1e+6);
}
log_write("[Menu::OnInstallStart] got state: %u\n", (u8)m_state);
if (m_source) {
log_write("[Menu::OnInstallStart] we have source\n");
for (;;) {
{
SCOPED_MUTEX(&m_source->m_mutex);
if (!m_source->m_active && INSTALL_STATE != InstallState::Progress) {
break;
}
if (GetToken().stop_requested()) {
return false;
}
}
svcSleepThread(1e+6);
}
log_write("[Menu::OnInstallStart] stopped polling source\n");
}
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