Add Borealis GUI for patch extraction on Switch.
All checks were successful
Build NRO / build (push) Successful in 1m48s
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:
134
source/extractor.cpp
Normal file
134
source/extractor.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include "extractor.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
static constexpr size_t READ_BUF_SIZE = 8192;
|
||||
|
||||
int PatchExtractor::mkdirs(const char* path) {
|
||||
char tmp[512];
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
size_t len = strlen(tmp);
|
||||
if (len == 0) return 0;
|
||||
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
|
||||
|
||||
for (char* p = tmp + 1; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
mkdir(tmp, 0755);
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
return mkdir(tmp, 0755);
|
||||
}
|
||||
|
||||
bool PatchExtractor::open() {
|
||||
zip = unzOpen(PATCHES_ZIP);
|
||||
if (!zip) return false;
|
||||
|
||||
unz_global_info gi{};
|
||||
if (unzGetGlobalInfo(zip, &gi) != UNZ_OK) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
total = gi.number_entry;
|
||||
extracted = 0;
|
||||
skipped = 0;
|
||||
finished = false;
|
||||
err = unzGoToFirstFile(zip);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PatchExtractor::close() {
|
||||
if (zip) {
|
||||
unzClose(zip);
|
||||
zip = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int PatchExtractor::getProgressPercent() const {
|
||||
if (total == 0) return 100;
|
||||
return static_cast<int>((extracted * 100) / total);
|
||||
}
|
||||
|
||||
bool PatchExtractor::step() {
|
||||
if (!zip || finished) return false;
|
||||
|
||||
if (err != UNZ_OK) {
|
||||
finished = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
char filename[512];
|
||||
char fullpath[1024];
|
||||
unz_file_info fi{};
|
||||
|
||||
unzGetCurrentFileInfo(zip, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0);
|
||||
snprintf(fullpath, sizeof(fullpath), "%s%s", EXTRACT_DIR, filename);
|
||||
currentFile = filename;
|
||||
|
||||
size_t flen = strlen(filename);
|
||||
if (flen > 0 && filename[flen - 1] == '/') {
|
||||
mkdirs(fullpath);
|
||||
} else {
|
||||
char dirpart[1024];
|
||||
snprintf(dirpart, sizeof(dirpart), "%s", fullpath);
|
||||
char* last_slash = strrchr(dirpart, '/');
|
||||
if (last_slash) {
|
||||
*last_slash = '\0';
|
||||
mkdirs(dirpart);
|
||||
}
|
||||
|
||||
if (unzOpenCurrentFile(zip) == UNZ_OK) {
|
||||
FILE* out = fopen(fullpath, "wb");
|
||||
if (out) {
|
||||
unsigned char buf[READ_BUF_SIZE];
|
||||
int bytes;
|
||||
while ((bytes = unzReadCurrentFile(zip, buf, READ_BUF_SIZE)) > 0) {
|
||||
fwrite(buf, 1, static_cast<size_t>(bytes), out);
|
||||
}
|
||||
fclose(out);
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
unzCloseCurrentFile(zip);
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
|
||||
extracted++;
|
||||
err = unzGoToNextFile(zip);
|
||||
if (err != UNZ_OK) finished = true;
|
||||
|
||||
return !finished;
|
||||
}
|
||||
|
||||
bool PatchExtractor::cleanup() {
|
||||
cleanupOk_ = true;
|
||||
|
||||
if (remove(PATCHES_ZIP) != 0)
|
||||
cleanupOk_ = false;
|
||||
|
||||
// Cannot delete our own NRO while the app is still running; try anyway for edge cases.
|
||||
remove(SELF_NRO);
|
||||
|
||||
remove("sdmc:/switch/.PatchExtractor.nro.star");
|
||||
remove("sdmc:/switch/.packages/boot_package.ini");
|
||||
|
||||
return cleanupOk_;
|
||||
}
|
||||
|
||||
void PatchExtractor::tryDeleteSelfOnExit() {
|
||||
#ifdef __SWITCH__
|
||||
// Embedded ROMFS keeps the .nro open on SD; unmount before unlink (console build had no ROMFS).
|
||||
romfsExit();
|
||||
#endif
|
||||
remove(SELF_NRO);
|
||||
}
|
||||
43
source/extractor.hpp
Normal file
43
source/extractor.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <minizip/unzip.h>
|
||||
#include <string>
|
||||
|
||||
class PatchExtractor {
|
||||
public:
|
||||
static constexpr const char* PATCHES_ZIP = "sdmc:/SaltySD/plugins/FPSLocker/patches.zip";
|
||||
static constexpr const char* EXTRACT_DIR = "sdmc:/SaltySD/plugins/FPSLocker/";
|
||||
static constexpr const char* SELF_NRO = "sdmc:/switch/PatchExtractor.nro";
|
||||
|
||||
bool open();
|
||||
void close();
|
||||
|
||||
/** Process one zip entry. Returns false when finished or on fatal error. */
|
||||
bool step();
|
||||
|
||||
bool isOpen() const { return zip != nullptr; }
|
||||
unsigned long getTotal() const { return total; }
|
||||
unsigned long getExtracted() const { return extracted; }
|
||||
unsigned long getSkipped() const { return skipped; }
|
||||
int getProgressPercent() const;
|
||||
const std::string& getCurrentFile() const { return currentFile; }
|
||||
bool isFinished() const { return finished; }
|
||||
|
||||
bool cleanup();
|
||||
bool cleanupOk() const { return cleanupOk_; }
|
||||
|
||||
/** Call after the UI has shut down; releases romfs so the NRO file can be unlinked. */
|
||||
static void tryDeleteSelfOnExit();
|
||||
|
||||
private:
|
||||
static int mkdirs(const char* path);
|
||||
|
||||
unzFile zip = nullptr;
|
||||
unsigned long total = 0;
|
||||
unsigned long extracted = 0;
|
||||
unsigned long skipped = 0;
|
||||
int err = UNZ_OK;
|
||||
bool finished = false;
|
||||
bool cleanupOk_ = true;
|
||||
std::string currentFile;
|
||||
};
|
||||
202
source/main.c
202
source/main.c
@@ -1,202 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <switch.h>
|
||||
#include <minizip/unzip.h>
|
||||
|
||||
#define PATCHES_ZIP "sdmc:/SaltySD/plugins/FPSLocker/patches.zip"
|
||||
#define EXTRACT_DIR "sdmc:/SaltySD/plugins/FPSLocker/"
|
||||
#define SELF_NRO "sdmc:/switch/PatchExtractor.nro"
|
||||
#define READ_BUF_SIZE 8192
|
||||
|
||||
#define STATUS_ROW 11
|
||||
|
||||
static PrintConsole *con;
|
||||
|
||||
static void status(const char *fmt, ...) {
|
||||
printf("\x1b[%d;0H\x1b[2K", STATUS_ROW);
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static int mkdirs(const char *path) {
|
||||
char tmp[512];
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
size_t len = strlen(tmp);
|
||||
if (len == 0) return 0;
|
||||
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
|
||||
|
||||
for (char *p = tmp + 1; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
mkdir(tmp, 0755);
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
return mkdir(tmp, 0755);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
con = consoleInit(NULL);
|
||||
|
||||
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
||||
PadState pad;
|
||||
padInitializeDefault(&pad);
|
||||
|
||||
printf("\n");
|
||||
printf(" ========================================\n");
|
||||
printf(" PatchExtractor - OmniNX OC\n");
|
||||
printf(" SaltyNX / FPSLocker Patch Installer\n");
|
||||
printf(" ========================================\n\n");
|
||||
|
||||
unzFile zip = unzOpen(PATCHES_ZIP);
|
||||
if (!zip) {
|
||||
printf(" \x1b[31mFEHLER:\x1b[0m patches.zip nicht gefunden!\n\n");
|
||||
printf(" Pfad: %s\n\n", PATCHES_ZIP);
|
||||
printf(" Bist du sicher, dass du OmniNX OC installiert hast?\n\n");
|
||||
printf(" Die Datei patches.zip muss unter\n");
|
||||
printf(" sd:/SaltySD/plugins/FPSLocker/ liegen.\n\n");
|
||||
printf(" ----------------------------------------\n");
|
||||
printf(" Druecke + zum Beenden.\n");
|
||||
|
||||
while (appletMainLoop()) {
|
||||
padUpdate(&pad);
|
||||
if (padGetButtonsDown(&pad) & HidNpadButton_Plus) break;
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
consoleExit(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unz_global_info gi;
|
||||
unzGetGlobalInfo(zip, &gi);
|
||||
unsigned long total = gi.number_entry;
|
||||
printf(" patches.zip gefunden! (%lu Eintraege)\n", total);
|
||||
printf(" Druecke A zum Entpacken, + zum Abbrechen.\n\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
while (appletMainLoop()) {
|
||||
padUpdate(&pad);
|
||||
u64 kDown = padGetButtonsDown(&pad);
|
||||
if (kDown & HidNpadButton_A) break;
|
||||
if (kDown & HidNpadButton_Plus) {
|
||||
unzClose(zip);
|
||||
consoleExit(NULL);
|
||||
return 0;
|
||||
}
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
printf("\x1b[10;0H \x1b[93mBitte nicht beenden, bis der Vorgang abgeschlossen ist!\x1b[0m\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
char filename[512];
|
||||
char fullpath[1024];
|
||||
unsigned char buf[READ_BUF_SIZE];
|
||||
unsigned long extracted = 0;
|
||||
unsigned long skipped = 0;
|
||||
int err = unzGoToFirstFile(zip);
|
||||
|
||||
while (err == UNZ_OK) {
|
||||
unz_file_info fi;
|
||||
unzGetCurrentFileInfo(zip, &fi, filename, sizeof(filename), NULL, 0, NULL, 0);
|
||||
|
||||
snprintf(fullpath, sizeof(fullpath), "%s%s", EXTRACT_DIR, filename);
|
||||
unsigned long progress = (unsigned long)((extracted * 100) / total);
|
||||
|
||||
size_t flen = strlen(filename);
|
||||
if (flen > 0 && filename[flen - 1] == '/') {
|
||||
mkdirs(fullpath);
|
||||
status(" [%3lu%%] DIR %s", progress, filename);
|
||||
} else {
|
||||
char dirpart[1024];
|
||||
snprintf(dirpart, sizeof(dirpart), "%s", fullpath);
|
||||
char *last_slash = strrchr(dirpart, '/');
|
||||
if (last_slash) {
|
||||
*last_slash = '\0';
|
||||
mkdirs(dirpart);
|
||||
}
|
||||
|
||||
if (unzOpenCurrentFile(zip) != UNZ_OK) {
|
||||
status(" [%3lu%%] \x1b[31mERR\x1b[0m %s", progress, filename);
|
||||
skipped++;
|
||||
extracted++;
|
||||
err = unzGoToNextFile(zip);
|
||||
continue;
|
||||
}
|
||||
|
||||
FILE *out = fopen(fullpath, "wb");
|
||||
if (!out) {
|
||||
status(" [%3lu%%] \x1b[31mERR\x1b[0m %s", progress, filename);
|
||||
unzCloseCurrentFile(zip);
|
||||
skipped++;
|
||||
extracted++;
|
||||
err = unzGoToNextFile(zip);
|
||||
continue;
|
||||
}
|
||||
|
||||
int bytes;
|
||||
while ((bytes = unzReadCurrentFile(zip, buf, READ_BUF_SIZE)) > 0) {
|
||||
fwrite(buf, 1, bytes, out);
|
||||
}
|
||||
|
||||
fclose(out);
|
||||
unzCloseCurrentFile(zip);
|
||||
status(" [%3lu%%] FILE %s", progress, filename);
|
||||
}
|
||||
|
||||
extracted++;
|
||||
consoleUpdate(NULL);
|
||||
err = unzGoToNextFile(zip);
|
||||
}
|
||||
|
||||
unzClose(zip);
|
||||
|
||||
status(" [100%%] Fertig! %lu / %lu entpackt.", extracted - skipped, total);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
int cleanup_ok = 1;
|
||||
|
||||
printf("\x1b[%d;0H", STATUS_ROW + 2);
|
||||
if (remove(PATCHES_ZIP) == 0) {
|
||||
printf(" patches.zip geloescht.\n");
|
||||
} else {
|
||||
printf(" \x1b[31mpatches.zip konnte nicht geloescht werden.\x1b[0m\n");
|
||||
cleanup_ok = 0;
|
||||
}
|
||||
|
||||
if (remove(SELF_NRO) == 0) {
|
||||
printf(" PatchExtractor.nro geloescht (Selbstreinigung).\n");
|
||||
} else {
|
||||
printf(" \x1b[31mPatchExtractor.nro konnte nicht geloescht werden.\x1b[0m\n");
|
||||
cleanup_ok = 0;
|
||||
}
|
||||
|
||||
remove("sdmc:/switch/.PatchExtractor.nro.star");
|
||||
remove("sdmc:/switch/.packages/boot_package.ini");
|
||||
|
||||
printf("\n ========================================\n");
|
||||
if (skipped == 0 && cleanup_ok) {
|
||||
printf(" \x1b[32mAlles erledigt!\x1b[0m\n");
|
||||
} else {
|
||||
printf(" \x1b[32mEntpacken abgeschlossen.\x1b[0m\n");
|
||||
if (skipped > 0)
|
||||
printf(" \x1b[31m%lu Eintraege fehlgeschlagen.\x1b[0m\n", skipped);
|
||||
}
|
||||
printf(" ========================================\n\n");
|
||||
printf(" Druecke + zum Beenden.\n");
|
||||
|
||||
while (appletMainLoop()) {
|
||||
padUpdate(&pad);
|
||||
if (padGetButtonsDown(&pad) & HidNpadButton_Plus) break;
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
consoleExit(NULL);
|
||||
return 0;
|
||||
}
|
||||
25
source/main.cpp
Normal file
25
source/main.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <borealis.hpp>
|
||||
|
||||
#include "extractor.hpp"
|
||||
#include "patch_activity.hpp"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
brls::Logger::setLogLevel(brls::LogLevel::WARNING);
|
||||
|
||||
if (!brls::Application::init()) {
|
||||
brls::Logger::error("Borealis init failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
brls::Application::createWindow("PatchExtractor");
|
||||
brls::Application::setGlobalQuit(true);
|
||||
brls::Application::pushActivity(new PatchActivity());
|
||||
|
||||
while (brls::Application::mainLoop()) {}
|
||||
|
||||
PatchExtractor::tryDeleteSelfOnExit();
|
||||
return 0;
|
||||
}
|
||||
201
source/patch_activity.cpp
Normal file
201
source/patch_activity.cpp
Normal 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);
|
||||
}
|
||||
39
source/patch_activity.hpp
Normal file
39
source/patch_activity.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <borealis.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include "extractor.hpp"
|
||||
|
||||
class PatchActivity : public brls::Activity {
|
||||
public:
|
||||
PatchActivity();
|
||||
~PatchActivity() override;
|
||||
|
||||
brls::View* createContentView() override;
|
||||
void onContentAvailable() override;
|
||||
|
||||
private:
|
||||
enum class Screen { Error, Ready, Extracting, Done };
|
||||
|
||||
void showScreen(Screen screen);
|
||||
void startExtraction();
|
||||
void onExtractTick();
|
||||
void updateProgressUi();
|
||||
bool onExtractClicked(brls::View* view);
|
||||
|
||||
brls::AppletFrame* frame = nullptr;
|
||||
brls::Box* contentBox = nullptr;
|
||||
|
||||
brls::Label* titleLabel = nullptr;
|
||||
brls::Label* messageLabel = nullptr;
|
||||
brls::Label* detailLabel = nullptr;
|
||||
brls::Label* progressLabel = nullptr;
|
||||
brls::Box* progressTrack = nullptr;
|
||||
brls::Rectangle* progressFill = nullptr;
|
||||
brls::Button* actionButton = nullptr;
|
||||
|
||||
std::unique_ptr<PatchExtractor> extractor;
|
||||
brls::RepeatingTimer extractTimer;
|
||||
Screen currentScreen = Screen::Ready;
|
||||
};
|
||||
Reference in New Issue
Block a user