filebrowser: add forwarder creator.

This commit is contained in:
ITotalJustice
2025-08-03 00:58:03 +01:00
parent 6554b68efa
commit a3780bdcea
4 changed files with 153 additions and 124 deletions

View File

@@ -140,7 +140,7 @@ public:
m_callback = cb;
}
auto GetValue() const {
auto GetValue() const -> const std::string& {
return m_value;
}
@@ -155,14 +155,12 @@ private:
class SidebarEntryTextInput final : public SidebarEntryTextBase {
public:
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide = {}, const std::string& info = "");
void SetGuide(const std::string& guide) {
m_guide = guide;
}
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide = {}, s64 len_min = -1, s64 len_max = FS_MAX_PATH, const std::string& info = "");
private:
std::string m_guide;
const std::string m_guide;
const s64 m_len_min;
const s64 m_len_max;
};
class SidebarEntryFilePicker final : public SidebarEntryTextBase {
@@ -178,7 +176,7 @@ private:
std::vector<std::string> m_filter{};
};
class Sidebar final : public Widget {
class Sidebar : public Widget {
public:
enum class Side { LEFT, RIGHT };
using Items = std::vector<std::unique_ptr<SidebarEntryBase>>;
@@ -197,8 +195,8 @@ public:
auto Add(std::unique_ptr<SidebarEntryBase>&& entry) -> SidebarEntryBase*;
template<DerivedFromSidebarBase T, typename... Args>
auto Add(Args&&... args) -> SidebarEntryBase* {
return Add(std::make_unique<T>(std::forward<Args>(args)...));
auto Add(Args&&... args) -> T* {
return (T*)Add(std::make_unique<T>(std::forward<Args>(args)...));
}
private:

View File

@@ -843,7 +843,7 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
pbox->SetImageDataConst(config.icon);
R_UNLESS(!config.nro_path.empty(), Result_OwoBadArgs);
// R_UNLESS(!config.icon.empty(), OwoError_BadArgs);
R_UNLESS(!config.icon.empty(), Result_OwoBadArgs);
R_TRY(splCryptoInitialize());
ON_SCOPE_EXIT(splCryptoExit());

View File

@@ -38,12 +38,33 @@
#include <span>
#include <utility>
#include <ranges>
// #include <stack>
#include <expected>
namespace sphaira::ui::menu::filebrowser {
namespace {
using RomDatabaseIndexs = std::vector<size_t>;
struct ForwarderForm final : public Sidebar {
explicit ForwarderForm(const FileAssocEntry& assoc, const RomDatabaseIndexs& db_indexs, const FileEntry& entry, const fs::FsPath& arg_path);
private:
auto LoadNroMeta() -> Result;
private:
const FileAssocEntry m_assoc;
const RomDatabaseIndexs m_db_indexs;
const fs::FsPath m_arg_path;
NroEntry m_nro{};
NacpStruct m_nacp{};
SidebarEntryTextInput* m_name{};
SidebarEntryTextInput* m_author{};
SidebarEntryTextInput* m_version{};
SidebarEntryFilePicker* m_icon{};
};
constinit UEvent g_change_uevent;
constexpr FsEntry FS_ENTRY_DEFAULT{
@@ -176,7 +197,6 @@ auto IsExtension(std::string_view ext1, std::string_view ext2) -> bool {
// tries to find database path using folder name
// names are taken from retropie
// retroarch database names can also be used
using RomDatabaseIndexs = std::vector<size_t>;
auto GetRomDatabaseFromPath(std::string_view path) -> RomDatabaseIndexs {
if (path.length() <= 1) {
return {};
@@ -216,7 +236,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> RomDatabaseIndexs {
}
//
auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDatabaseIndexs& db_indexs, const NroEntry& nro) {
auto GetRomIcon(std::string filename, const RomDatabaseIndexs& db_indexs, const NroEntry& nro) {
// if no db entries, use nro icon
if (db_indexs.empty()) {
log_write("using nro image\n");
@@ -234,8 +254,6 @@ auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDa
}
}
#define RA_BOXART_URL "https://thumbnails.libretro.com/"
#define GH_BOXART_URL "https://raw.githubusercontent.com/libretro-thumbnails/"
#define RA_BOXART_NAME "/Named_Boxarts/"
#define RA_THUMBNAIL_PATH "/retroarch/thumbnails/"
#define RA_BOXART_EXT ".png"
@@ -249,50 +267,133 @@ auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDa
}
}
std::string filename_gh;
filename_gh.reserve(filename.size());
for (auto c : filename) {
if (c == ' ') {
filename_gh += "%20";
} else {
filename_gh.push_back(c);
}
}
const std::string thumbnail_path = system_name + RA_BOXART_NAME + filename + RA_BOXART_EXT;
const std::string ra_thumbnail_path = RA_THUMBNAIL_PATH + thumbnail_path;
const std::string ra_thumbnail_url = RA_BOXART_URL + thumbnail_path;
const std::string gh_thumbnail_url = GH_BOXART_URL + system_name_gh + RA_BOXART_NAME + filename_gh + RA_BOXART_EXT;
log_write("starting image convert on: %s\n", ra_thumbnail_path.c_str());
// try and find icon locally
if (!pbox->ShouldExit()) {
pbox->NewTransfer("Trying to load "_i18n + ra_thumbnail_path);
std::vector<u8> image_file;
if (R_SUCCEEDED(fs->read_entire_file(ra_thumbnail_path.c_str(), image_file))) {
if (R_SUCCEEDED(fs::FsNativeSd().read_entire_file(ra_thumbnail_path, image_file))) {
return image_file;
}
}
// try and download icon
if (!pbox->ShouldExit()) {
pbox->NewTransfer("Downloading "_i18n + gh_thumbnail_url);
const auto result = curl::Api().ToMemory(
curl::Url{gh_thumbnail_url},
curl::OnProgress{pbox->OnDownloadProgressCallback()}
);
if (result.success && !result.data.empty()) {
return result.data;
}
}
}
// use nro icon
log_write("using nro image\n");
return nro_get_icon(nro.path, nro.icon_size, nro.icon_offset);
}
ForwarderForm::ForwarderForm(const FileAssocEntry& assoc, const RomDatabaseIndexs& db_indexs, const FileEntry& entry, const fs::FsPath& arg_path)
: Sidebar{"Forwarder Creation", Side::RIGHT}
, m_assoc{assoc}
, m_db_indexs{db_indexs}
, m_arg_path{arg_path} {
log_write("parsing nro\n");
if (R_FAILED(LoadNroMeta())) {
App::Notify("Failed to parse nro"_i18n);
SetPop();
return;
}
log_write("got nro data\n");
auto file_name = m_assoc.use_base_name ? entry.GetName() : entry.GetInternalName();
if (auto pos = file_name.find_last_of('.'); pos != std::string::npos) {
log_write("got filename\n");
file_name = file_name.substr(0, pos);
log_write("got filename2: %s\n\n", file_name.c_str());
}
const auto name = m_nro.nacp.lang.name + std::string{" | "} + file_name;
const auto author = m_nacp.lang[0].author;
const auto version = m_nacp.display_version;
const auto icon = m_assoc.path;
m_name = this->Add<SidebarEntryTextInput>(
"Name", name, "", -1, sizeof(NacpLanguageEntry::name) - 1,
"Set the name of the application"_i18n
);
m_author = this->Add<SidebarEntryTextInput>(
"Author", author, "", -1, sizeof(NacpLanguageEntry::author) - 1,
"Set the author of the application"_i18n
);
m_version = this->Add<SidebarEntryTextInput>(
"Version", version, "", -1, sizeof(NacpStruct::display_version) - 1,
"Set the display version of the application"_i18n
);
const std::vector<std::string> filters{".nro", ".png", ".jpg"};
m_icon = this->Add<SidebarEntryFilePicker>(
"Icon", icon, filters,
"Set the path to the icon for the forwarder"_i18n
);
auto callback = this->Add<SidebarEntryCallback>("Create", [this, file_name](){
OwoConfig config{};
config.nro_path = m_assoc.path.toString();
config.args = nro_add_arg_file(m_arg_path);
config.nacp = m_nacp;
// patch the name.
config.name = m_name->GetValue();
// patch the author.
config.author = m_author->GetValue();
// patch the display version.
std::snprintf(config.nacp.display_version, sizeof(config.nacp.display_version), "%s", m_version->GetValue().c_str());
// load icon fron nro or image.
if (m_icon->GetValue().ends_with(".nro")) {
// if path was left as the default, try and load the icon from rom db.
if (config.nro_path == m_icon->GetValue()) {
config.icon = GetRomIcon(file_name, m_db_indexs, m_nro);
} else {
config.icon = nro_get_icon(m_icon->GetValue());
}
} else {
// try and read icon file into memory, bail if this fails.
const auto rc = fs::FsStdio().read_entire_file(m_icon->GetValue(), config.icon);
if (R_FAILED(rc)) {
App::PushErrorBox(rc, "Failed to load icon");
return;
}
}
// if this is a rom, load intro logo.
if (!m_db_indexs.empty()) {
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/NintendoLogo.png", config.logo);
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/StartupMovie.gif", config.gif);
}
// try and install.
if (R_FAILED(App::Install(config))) {
App::Notify("Failed to install forwarder"_i18n);
} else {
SetPop();
}
}, "Create the forwarder."_i18n);
// ensure that all fields are valid.
callback->Depends([this](){
return
!m_name->GetValue().empty() &&
!m_author->GetValue().empty() &&
!m_version->GetValue().empty() &&
!m_icon->GetValue().empty();
}, "All fields must be non-empty!"_i18n);
}
auto ForwarderForm::LoadNroMeta() -> Result {
// try and load nro meta data.
R_TRY(nro_parse(m_assoc.path, m_nro));
R_TRY(nro_get_nacp(m_assoc.path, m_nacp));
R_SUCCEED();
}
} // namespace
void SignalChange() {
@@ -650,51 +751,7 @@ void FsView::InstallForwarder() {
title, items, [this, assoc_list](auto op_index){
if (op_index) {
const auto assoc = assoc_list[*op_index];
log_write("pushing it\n");
App::Push<ProgressBox>(0, "Installing Forwarder"_i18n, GetEntry().name, [assoc, this](auto pbox) -> Result {
log_write("inside callback\n");
NroEntry nro{};
log_write("parsing nro\n");
R_TRY(nro_parse(assoc.path, nro));
NacpStruct nacp;
R_TRY(nro_get_nacp(assoc.path, nacp));
log_write("got nro data\n");
auto file_name = assoc.use_base_name ? GetEntry().GetName() : GetEntry().GetInternalName();
if (auto pos = file_name.find_last_of('.'); pos != std::string::npos) {
log_write("got filename\n");
file_name = file_name.substr(0, pos);
log_write("got filename2: %s\n\n", file_name.c_str());
}
const auto db_indexs = GetRomDatabaseFromPath(m_path);
OwoConfig config{};
config.nro_path = assoc.path.toString();
config.args = nro_add_arg_file(GetNewPathCurrent());
config.name = nro.nacp.lang.name + std::string{" | "} + file_name;
// config.name = file_name;
config.nacp = nacp;
config.icon = GetRomIcon(m_fs.get(), pbox, file_name, db_indexs, nro);
pbox->SetImageDataConst(config.icon);
if (!db_indexs.empty()) {
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/NintendoLogo.png", config.logo);
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/StartupMovie.gif", config.gif);
}
return App::Install(pbox, config);
}, [this](Result rc){
App::PushErrorBox(rc, "Failed to install forwarder"_i18n);
if (R_SUCCEEDED(rc)) {
App::PlaySoundEffect(SoundEffect_Install);
App::Notify("Installed!"_i18n);
}
});
App::Push<ForwarderForm>(assoc, GetRomDatabaseFromPath(m_path), GetEntry(), GetNewPathCurrent());
} else {
log_write("pressed B to skip launch...\n");
}

View File

@@ -252,19 +252,17 @@ SidebarEntryTextBase::SidebarEntryTextBase(const std::string& title, const std::
void SidebarEntryTextBase::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) {
SidebarEntryBase::Draw(vg, theme, root_pos, left);
SidebarEntryBase::DrawEntry(vg, theme, m_title, m_value, true);
// const auto colour_id = IsEnabled() ? ThemeEntryID_TEXT : ThemeEntryID_TEXT_INFO;
// const auto max_w = m_pos.w - 15.f * 2;
// m_scolling_title.Draw(vg, HasFocus(), m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f), max_w, 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour_id), m_title);
}
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide, const std::string& info)
: SidebarEntryTextBase{title, value, {}, info}, m_guide{guide} {
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide, s64 len_min, s64 len_max, const std::string& info)
: SidebarEntryTextBase{title, value, {}, info}
, m_guide{guide}
, m_len_min{len_min}
, m_len_max{len_max} {
SetCallback([this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, m_guide.c_str(), GetValue().c_str()))) {
if (R_SUCCEEDED(swkbd::ShowText(out, m_guide.c_str(), GetValue().c_str(), m_len_min, m_len_max))) {
SetValue(out);
}
});
@@ -347,30 +345,6 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
}
auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
// Vec4 info_box{};
// info_box.y = m_top_bar.y;
// info_box.w = 300;
// info_box.h = 250;
// if (m_side == Side::LEFT) {
// info_box.x = m_pos.x + m_pos.w + 10;
// } else {
// info_box.x = m_pos.x - info_box.w - 10;
// }
// const float info_pad = 30;
// const float info_font_size = 18;
// const char* msg = "Skips verifying the nca header signature";
// float bounds[4];
// nvgFontSize(vg, info_font_size);
// nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
// nvgTextLineHeight(vg, 1.7);
// nvgTextBoxBounds(vg, info_box.x + info_pad, info_box.y + info_pad, info_box.w - info_pad * 2, msg, nullptr, bounds);
// info_box.h = info_pad * 2 + bounds[3] - bounds[1];
// gfx::drawRect(vg, info_box, theme->GetColour(ThemeEntryID_SIDEBAR), 5);
// gfx::drawTextBox(vg, bounds[0], bounds[1], info_font_size, info_box.w - info_pad * 2, theme->GetColour(ThemeEntryID_TEXT), msg);
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR));
gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
if (!m_sub.empty()) {