Add Borealis GUI for patch extraction on Switch.
All checks were successful
Build NRO / build (push) Successful in 1m48s

Replace the console UI with a Borealis-based flow, bundle ROMFS assets and
borealis as a submodule, and apply small upstream patches at build time.
Self-delete runs after romfsExit on quit so the NRO can be removed like the
old console build.
This commit is contained in:
2026-05-28 22:22:44 +02:00
parent 828edf6ad2
commit 2cf3db2097
19 changed files with 956 additions and 214 deletions

201
source/patch_activity.cpp Normal file
View File

@@ -0,0 +1,201 @@
#include "patch_activity.hpp"
#include <cstdio>
PatchActivity::PatchActivity() {
extractor = std::make_unique<PatchExtractor>();
extractTimer.setCallback([this]() { onExtractTick(); });
extractTimer.setPeriod(0);
}
PatchActivity::~PatchActivity() {
extractTimer.stop();
if (extractor) extractor->close();
}
brls::View* PatchActivity::createContentView() {
frame = new brls::AppletFrame();
frame->setTitle("PatchExtractor");
frame->setIconFromRes("img/omninx.png");
contentBox = new brls::Box(brls::Axis::COLUMN);
contentBox->setAlignItems(brls::AlignItems::CENTER);
contentBox->setJustifyContent(brls::JustifyContent::CENTER);
contentBox->setMargins(40, 40, 40, 40);
contentBox->setGrow(1.0f);
titleLabel = new brls::Label();
titleLabel->setText("PatchExtractor");
titleLabel->setFontSize(28);
titleLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
titleLabel->setMarginBottom(16);
messageLabel = new brls::Label();
messageLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
messageLabel->setMarginBottom(12);
detailLabel = new brls::Label();
detailLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
detailLabel->setFontSize(14);
detailLabel->setMarginBottom(24);
detailLabel->setVisibility(brls::Visibility::GONE);
progressLabel = new brls::Label();
progressLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
progressLabel->setFontSize(16);
progressLabel->setMarginBottom(8);
progressLabel->setVisibility(brls::Visibility::GONE);
progressTrack = new brls::Box(brls::Axis::ROW);
progressTrack->setWidth(600);
progressTrack->setHeight(12);
progressTrack->setMarginBottom(24);
progressTrack->setVisibility(brls::Visibility::GONE);
progressFill = new brls::Rectangle(nvgRGB(0, 190, 80));
progressFill->setHeight(12);
progressFill->setWidth(0);
progressTrack->addView(progressFill);
actionButton = new brls::Button();
actionButton->setStyle(&brls::BUTTONSTYLE_PRIMARY);
actionButton->setWidth(320);
actionButton->registerClickAction([this](brls::View* view) { return onExtractClicked(view); });
contentBox->addView(titleLabel);
contentBox->addView(messageLabel);
contentBox->addView(detailLabel);
contentBox->addView(progressLabel);
contentBox->addView(progressTrack);
contentBox->addView(actionButton);
frame->setContentView(contentBox);
return frame;
}
void PatchActivity::onContentAvailable() {
registerExitAction(brls::BUTTON_START);
if (extractor->open()) {
char detail[128];
snprintf(detail, sizeof(detail), "%lu Eintraege in patches.zip", extractor->getTotal());
detailLabel->setText(detail);
showScreen(Screen::Ready);
} else {
detailLabel->setText(PatchExtractor::PATCHES_ZIP);
detailLabel->setVisibility(brls::Visibility::VISIBLE);
showScreen(Screen::Error);
}
}
void PatchActivity::showScreen(Screen screen) {
currentScreen = screen;
progressLabel->setVisibility(brls::Visibility::GONE);
progressTrack->setVisibility(brls::Visibility::GONE);
actionButton->setVisibility(brls::Visibility::VISIBLE);
switch (screen) {
case Screen::Error:
titleLabel->setText("Fehler");
messageLabel->setText("patches.zip nicht gefunden!");
detailLabel->setVisibility(brls::Visibility::VISIBLE);
actionButton->setText("Beenden");
actionButton->registerClickAction([](brls::View*) {
brls::Application::quit();
return true;
});
brls::Application::giveFocus(actionButton);
break;
case Screen::Ready:
titleLabel->setText("OmniNX OC");
messageLabel->setText("SaltyNX / FPSLocker Patch Installer");
detailLabel->setVisibility(brls::Visibility::VISIBLE);
actionButton->setText("Entpacken");
actionButton->registerClickAction([this](brls::View* view) { return onExtractClicked(view); });
brls::Application::giveFocus(actionButton);
break;
case Screen::Extracting:
titleLabel->setText("Entpacken...");
messageLabel->setText("Bitte nicht beenden, bis der Vorgang abgeschlossen ist!");
detailLabel->setVisibility(brls::Visibility::VISIBLE);
progressLabel->setVisibility(brls::Visibility::VISIBLE);
progressTrack->setVisibility(brls::Visibility::VISIBLE);
actionButton->setVisibility(brls::Visibility::GONE);
break;
case Screen::Done: {
titleLabel->setText("Fertig");
const unsigned long ok = extractor->getExtracted() - extractor->getSkipped();
char msg[128];
if (extractor->getSkipped() == 0 && extractor->cleanupOk())
snprintf(msg, sizeof(msg), "Alles erledigt! %lu / %lu entpackt.", ok, extractor->getTotal());
else
snprintf(msg, sizeof(msg), "Entpacken abgeschlossen. %lu / %lu entpackt.", ok, extractor->getTotal());
messageLabel->setText(msg);
if (extractor->getSkipped() > 0) {
char err[64];
snprintf(err, sizeof(err), "%lu Eintraege fehlgeschlagen.", extractor->getSkipped());
detailLabel->setText(err);
detailLabel->setVisibility(brls::Visibility::VISIBLE);
} else if (!extractor->cleanupOk()) {
detailLabel->setText("Aufräumen teilweise fehlgeschlagen.");
detailLabel->setVisibility(brls::Visibility::VISIBLE);
} else {
detailLabel->setVisibility(brls::Visibility::GONE);
}
actionButton->setText("Beenden");
actionButton->setVisibility(brls::Visibility::VISIBLE);
actionButton->registerClickAction([](brls::View*) {
brls::Application::quit();
return true;
});
brls::Application::unblockInputs();
brls::Application::giveFocus(actionButton);
break;
}
}
}
bool PatchActivity::onExtractClicked(brls::View* view) {
(void)view;
if (currentScreen != Screen::Ready) return false;
startExtraction();
return true;
}
void PatchActivity::startExtraction() {
showScreen(Screen::Extracting);
brls::Application::blockInputs();
updateProgressUi();
extractTimer.start();
}
void PatchActivity::onExtractTick() {
if (!extractor->step()) {
extractTimer.stop();
extractor->close();
extractor->cleanup();
updateProgressUi();
showScreen(Screen::Done);
return;
}
updateProgressUi();
}
void PatchActivity::updateProgressUi() {
const int pct = extractor->getProgressPercent();
progressLabel->setText(std::to_string(pct) + "%");
constexpr float trackWidth = 600.0f;
progressFill->setWidth(trackWidth * static_cast<float>(pct) / 100.0f);
const std::string& file = extractor->getCurrentFile();
if (!file.empty())
detailLabel->setText(file);
}