diff --git a/assets/romfs/GET.png b/assets/embed/icons/GET.png similarity index 100% rename from assets/romfs/GET.png rename to assets/embed/icons/GET.png diff --git a/assets/romfs/INSTALLED.png b/assets/embed/icons/INSTALLED.png similarity index 100% rename from assets/romfs/INSTALLED.png rename to assets/embed/icons/INSTALLED.png diff --git a/assets/romfs/LOCAL.png b/assets/embed/icons/LOCAL.png similarity index 100% rename from assets/romfs/LOCAL.png rename to assets/embed/icons/LOCAL.png diff --git a/assets/romfs/UPDATE.png b/assets/embed/icons/UPDATE.png similarity index 100% rename from assets/romfs/UPDATE.png rename to assets/embed/icons/UPDATE.png diff --git a/assets/romfs/default.png b/assets/embed/icons/default.png similarity index 100% rename from assets/romfs/default.png rename to assets/embed/icons/default.png diff --git a/hbl/CMakeLists.txt b/hbl/CMakeLists.txt index 1147739..b3a3e06 100644 --- a/hbl/CMakeLists.txt +++ b/hbl/CMakeLists.txt @@ -78,11 +78,13 @@ function(nx_create_npdm target config) dkp_set_target_file(${outtarget} "${NX_NPDM_OUTPUT}") endfunction() +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/exefs) + nx_create_nso(hbl - OUTPUT main + OUTPUT exefs/main ) nx_create_npdm(hbl - OUTPUT main.npdm + OUTPUT exefs/main.npdm CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/hbl.json ) diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index 08e7c97..c629e27 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -147,8 +147,6 @@ target_compile_options(sphaira PRIVATE -Wimplicit-fallthrough=5 -Wsuggest-final-types -Wuninitialized - -fimplicit-constexpr - -Wmissing-requires ) include(FetchContent) @@ -314,9 +312,9 @@ if (NOT USE_NEW_ZSTD) endif() set_target_properties(sphaira PROPERTIES - C_STANDARD 11 + C_STANDARD 23 C_EXTENSIONS ON - CXX_STANDARD 23 + CXX_STANDARD 26 CXX_EXTENSIONS ON ) @@ -357,12 +355,13 @@ file(COPY ${CMAKE_SOURCE_DIR}/assets/romfs DESTINATION ${CMAKE_CURRENT_BINARY_DI # create assets target dkp_add_asset_target(sphaira_romfs ${CMAKE_CURRENT_BINARY_DIR}/romfs) -# add hbl exefs to romfs, used for forwarders -dkp_install_assets(sphaira_romfs - DESTINATION exefs - TARGETS - hbl_nso - hbl_npdm +# wait until hbl is built first as we need the exefs to embed +add_dependencies(sphaira hbl_nso hbl_npdm) + +# set the embed path for assets and hbl +target_compile_options(sphaira PRIVATE + --embed-dir=${CMAKE_SOURCE_DIR}/assets/embed + --embed-dir=${CMAKE_BINARY_DIR}/hbl ) # add nanovg shaders to romfs diff --git a/sphaira/include/app.hpp b/sphaira/include/app.hpp index 67be96c..b1348a5 100644 --- a/sphaira/include/app.hpp +++ b/sphaira/include/app.hpp @@ -69,6 +69,7 @@ public: static auto GetThemeIndex() -> s64; static auto GetDefaultImage() -> int; + static auto GetDefaultImageData() -> std::span; // returns argv[0] static auto GetExePath() -> fs::FsPath; @@ -196,6 +197,7 @@ public: static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini"; static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini"; static constexpr inline auto INI_SECTION = "config"; + static constexpr inline auto DEFAULT_THEME_PATH = "romfs:/themes/abyss_theme.ini"; fs::FsPath m_app_path; u64 m_start_timestamp{}; @@ -229,17 +231,18 @@ public: option::OptionBool m_hdd_enabled{INI_SECTION, "hdd_enabled", false}; option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false}; option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false}; + option::OptionString m_theme_path{INI_SECTION, "theme", DEFAULT_THEME_PATH}; option::OptionBool m_theme_music{INI_SECTION, "theme_music", true}; option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false}; option::OptionLong m_language{INI_SECTION, "language", 0}; // auto option::OptionString m_right_side_menu{INI_SECTION, "right_side_menu", "Appstore"}; + option::OptionBool m_progress_boost_mode{INI_SECTION, "progress_boost_mode", false}; // install options option::OptionBool m_install_sysmmc{INI_SECTION, "install_sysmmc", false}; option::OptionBool m_install_emummc{INI_SECTION, "install_emummc", false}; option::OptionBool m_install_sd{INI_SECTION, "install_sd", true}; option::OptionLong m_install_prompt{INI_SECTION, "install_prompt", true}; - option::OptionLong m_boost_mode{INI_SECTION, "boost_mode", false}; option::OptionBool m_allow_downgrade{INI_SECTION, "allow_downgrade", false}; option::OptionBool m_skip_if_already_installed{INI_SECTION, "skip_if_already_installed", true}; option::OptionBool m_ticket_only{INI_SECTION, "ticket_only", false}; diff --git a/sphaira/include/nro.hpp b/sphaira/include/nro.hpp index 5317477..01199a2 100644 --- a/sphaira/include/nro.hpp +++ b/sphaira/include/nro.hpp @@ -11,13 +11,17 @@ namespace sphaira { struct Hbini { u64 timestamp{}; // timestamp of last launch - u32 launch_count{}; // +}; + +struct MiniNacp { + NacpLanguageEntry lang; + char display_version[0x10]; }; struct NroEntry { fs::FsPath path{}; s64 size{}; - NacpStruct nacp{}; + MiniNacp nacp{}; u64 icon_size{}; u64 icon_offset{}; @@ -31,11 +35,11 @@ struct NroEntry { std::optional has_star{std::nullopt}; auto GetName() const -> const char* { - return nacp.lang[0].name; + return nacp.lang.name; } auto GetAuthor() const -> const char* { - return nacp.lang[0].author; + return nacp.lang.author; } auto GetDisplayVersion() const -> const char* { diff --git a/sphaira/include/option.hpp b/sphaira/include/option.hpp index 1625176..4eb6204 100644 --- a/sphaira/include/option.hpp +++ b/sphaira/include/option.hpp @@ -17,6 +17,11 @@ struct OptionBase { auto GetOr(const char* name) -> T; void Set(T value); + // returns true if loaded. + auto LoadFrom(const char* section, const char* name, const char* value) -> bool; + // same as above, but only checks the name. + auto LoadFrom(const char* name, const char* value) -> bool; + private: auto GetInternal(const char* name) -> T; diff --git a/sphaira/include/owo.hpp b/sphaira/include/owo.hpp index ed2a6f7..c180aa5 100644 --- a/sphaira/include/owo.hpp +++ b/sphaira/include/owo.hpp @@ -4,7 +4,6 @@ #include #include #include "ui/progress_box.hpp" -// #include namespace sphaira { @@ -15,12 +14,9 @@ struct OwoConfig { std::string author{}; NacpStruct nacp; std::vector icon; - std::vector main; - std::vector npdm; std::vector logo; std::vector gif; - // std::optional tid; std::vector program_nca{}; }; diff --git a/sphaira/include/ui/menus/homebrew.hpp b/sphaira/include/ui/menus/homebrew.hpp index 777edf1..685ef72 100644 --- a/sphaira/include/ui/menus/homebrew.hpp +++ b/sphaira/include/ui/menus/homebrew.hpp @@ -37,7 +37,7 @@ struct Menu final : grid::Menu { return m_entries; } - static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector& icon); + static Result InstallHomebrew(const fs::FsPath& path, const std::vector& icon); static Result InstallHomebrewFromPath(const fs::FsPath& path); private: diff --git a/sphaira/include/yati/yati.hpp b/sphaira/include/yati/yati.hpp index 7404e04..27f41bb 100644 --- a/sphaira/include/yati/yati.hpp +++ b/sphaira/include/yati/yati.hpp @@ -70,10 +70,6 @@ enum : Result { struct Config { bool sd_card_install{}; - // sets the performance mode to FastLoad which boosts the CPU clock - // and lowers the GPU clock. - bool boost_mode{}; - // enables downgrading patch / data patch (dlc) version. bool allow_downgrade{}; diff --git a/sphaira/source/app.cpp b/sphaira/source/app.cpp index 6688691..e4893b2 100644 --- a/sphaira/source/app.cpp +++ b/sphaira/source/app.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,10 @@ constexpr fs::FsPath DEFAULT_MUSIC_PATH = "/config/sphaira/themes/default_music. constexpr const char* DEFAULT_MUSIC_URL = "https://files.catbox.moe/1ovji1.bfstm"; // constexpr const char* DEFAULT_MUSIC_URL = "https://raw.githubusercontent.com/ITotalJustice/sphaira/refs/heads/master/assets/default_music.bfstm"; +constexpr const u8 DEFAULT_IMAGE_DATA[]{ + #embed +}; + void download_default_music() { App::Push(std::make_shared(0, "Downloading "_i18n, "default_music.bfstm", [](auto pbox){ const auto result = curl::Api().ToFile( @@ -444,10 +449,7 @@ void App::Loop() { // update timestamp ini_putl(nro_path.c_str(), "timestamp", timestamp, App::PLAYLOG_PATH); - // update launch_count - const long old_launch_count = ini_getl(nro_path.c_str(), "launch_count", 0, App::PLAYLOG_PATH); - ini_putl(nro_path.c_str(), "launch_count", old_launch_count + 1, App::PLAYLOG_PATH); - log_write("updating timestamp and launch count for: %s %lu %ld\n", nro_path.c_str(), timestamp, old_launch_count + 1); + log_write("updating timestamp for: %s %lu\n", nro_path.c_str(), timestamp); // force disable pop-back to main menu. __nx_applet_exit_mode = 0; @@ -516,11 +518,11 @@ auto App::Push(std::shared_ptr widget) -> void { } auto App::PopToMenu() -> void { - for (auto it = g_app->m_widgets.rbegin(); it != g_app->m_widgets.rend(); it++) { - const auto& p = *it; + for (auto& p : std::ranges::views::reverse(g_app->m_widgets)) { if (p->IsMenu()) { break; } + p->SetPop(); } } @@ -595,6 +597,10 @@ auto App::GetDefaultImage() -> int { return g_app->m_default_image; } +auto App::GetDefaultImageData() -> std::span { + return DEFAULT_IMAGE_DATA; +} + auto App::GetExePath() -> fs::FsPath { return g_app->m_app_path; } @@ -868,18 +874,7 @@ void App::SetTextScrollSpeed(long index) { } auto App::Install(OwoConfig& config) -> Result { - R_TRY(romfsInit()); - ON_SCOPE_EXIT(romfsExit()); - - std::vector main_data, npdm_data, logo_data, gif_data; - R_TRY(fs::read_entire_file("romfs:/exefs/main", main_data)); - R_TRY(fs::read_entire_file("romfs:/exefs/main.npdm", npdm_data)); - config.nro_path = nro_add_arg_file(config.nro_path); - config.main = main_data; - config.npdm = npdm_data; - config.logo = logo_data; - config.gif = gif_data; if (!config.icon.empty()) { config.icon = GetNroIcon(config.icon); } @@ -898,18 +893,7 @@ auto App::Install(OwoConfig& config) -> Result { } auto App::Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result { - R_TRY(romfsInit()); - ON_SCOPE_EXIT(romfsExit()); - - std::vector main_data, npdm_data, logo_data, gif_data; - R_TRY(fs::read_entire_file("romfs:/exefs/main", main_data)); - R_TRY(fs::read_entire_file("romfs:/exefs/main.npdm", npdm_data)); - config.nro_path = nro_add_arg_file(config.nro_path); - config.main = main_data; - config.npdm = npdm_data; - config.logo = logo_data; - config.gif = gif_data; if (!config.icon.empty()) { config.icon = GetNroIcon(config.icon); } @@ -1268,6 +1252,9 @@ void App::ScanThemeEntries() { App::App(const char* argv0) { TimeStamp ts; + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + g_app = this; m_start_timestamp = armGetSystemTick(); if (!std::strncmp(argv0, "sdmc:/", 6)) { @@ -1283,10 +1270,55 @@ App::App(const char* argv0) { } fs::FsNativeSd fs; - fs.CreateDirectoryRecursively("/config/sphaira/assoc"); - fs.CreateDirectoryRecursively("/config/sphaira/themes"); - fs.CreateDirectoryRecursively("/config/sphaira/github"); - fs.CreateDirectoryRecursively("/config/sphaira/i18n"); + fs.CreateDirectoryRecursively("/config/sphaira"); + fs.CreateDirectory("/config/sphaira/assoc"); + fs.CreateDirectory("/config/sphaira/themes"); + fs.CreateDirectory("/config/sphaira/github"); + fs.CreateDirectory("/config/sphaira/i18n"); + + auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int { + auto app = static_cast(UserData); + + if (!std::strcmp(Section, INI_SECTION)) { + if (app->m_nxlink_enabled.LoadFrom(Key, Value)) {} + else if (app->m_mtp_enabled.LoadFrom(Key, Value)) {} + else if (app->m_ftp_enabled.LoadFrom(Key, Value)) {} + else if (app->m_hdd_enabled.LoadFrom(Key, Value)) {} + else if (app->m_log_enabled.LoadFrom(Key, Value)) {} + else if (app->m_replace_hbmenu.LoadFrom(Key, Value)) {} + else if (app->m_theme_path.LoadFrom(Key, Value)) {} + else if (app->m_theme_music.LoadFrom(Key, Value)) {} + else if (app->m_12hour_time.LoadFrom(Key, Value)) {} + else if (app->m_language.LoadFrom(Key, Value)) {} + else if (app->m_right_side_menu.LoadFrom(Key, Value)) {} + else if (app->m_install_sysmmc.LoadFrom(Key, Value)) {} + else if (app->m_install_emummc.LoadFrom(Key, Value)) {} + else if (app->m_install_sd.LoadFrom(Key, Value)) {} + else if (app->m_install_prompt.LoadFrom(Key, Value)) {} + else if (app->m_progress_boost_mode.LoadFrom(Key, Value)) {} + else if (app->m_allow_downgrade.LoadFrom(Key, Value)) {} + else if (app->m_skip_if_already_installed.LoadFrom(Key, Value)) {} + else if (app->m_ticket_only.LoadFrom(Key, Value)) {} + else if (app->m_skip_base.LoadFrom(Key, Value)) {} + else if (app->m_skip_patch.LoadFrom(Key, Value)) {} + else if (app->m_skip_addon.LoadFrom(Key, Value)) {} + else if (app->m_skip_data_patch.LoadFrom(Key, Value)) {} + else if (app->m_skip_ticket.LoadFrom(Key, Value)) {} + else if (app->m_skip_nca_hash_verify.LoadFrom(Key, Value)) {} + else if (app->m_skip_rsa_header_fixed_key_verify.LoadFrom(Key, Value)) {} + else if (app->m_skip_rsa_npdm_fixed_key_verify.LoadFrom(Key, Value)) {} + else if (app->m_ignore_distribution_bit.LoadFrom(Key, Value)) {} + else if (app->m_convert_to_standard_crypto.LoadFrom(Key, Value)) {} + else if (app->m_lower_master_key.LoadFrom(Key, Value)) {} + else if (app->m_lower_system_version.LoadFrom(Key, Value)) {} + } + + return 1; + }; + + // load all configs ahead of time, as this is actually faster than + // loading each config one by one as it avoids re-opening the file multiple times. + ini_browse(cb, this, CONFIG_PATH); if (App::GetLogEnable()) { log_file_init(); @@ -1409,17 +1441,14 @@ App::App(const char* argv0) { ScanThemeEntries(); - fs::FsPath theme_path{}; - constexpr fs::FsPath default_theme_path{"romfs:/themes/abyss_theme.ini"}; - ini_gets("config", "theme", default_theme_path, theme_path, sizeof(theme_path), CONFIG_PATH); - // try and load previous theme, default to previous version otherwise. + fs::FsPath theme_path = m_theme_path.Get(); ThemeMeta theme_meta; if (R_SUCCEEDED(romfsInit())) { ON_SCOPE_EXIT(romfsExit()); if (!LoadThemeMeta(theme_path, theme_meta)) { log_write("failed to load meta using default\n"); - theme_path = default_theme_path; + theme_path = DEFAULT_THEME_PATH; LoadThemeMeta(theme_path, theme_meta); } } @@ -1456,20 +1485,12 @@ App::App(const char* argv0) { } ini_putl(GetExePath(), "timestamp", m_start_timestamp, App::PLAYLOG_PATH); - const long old_launch_count = ini_getl(GetExePath(), "launch_count", 0, App::PLAYLOG_PATH); - ini_putl(GetExePath(), "launch_count", old_launch_count + 1, App::PLAYLOG_PATH); // load default image - if (R_SUCCEEDED(romfsInit())) { - ON_SCOPE_EXIT(romfsExit()); - const auto image = ImageLoadFromFile("romfs:/default.png"); - if (!image.data.empty()) { - m_default_image = nvgCreateImageRGBA(vg, image.w, image.h, 0, image.data.data()); - } - } + m_default_image = nvgCreateImageMem(vg, 0, DEFAULT_IMAGE_DATA, std::size(DEFAULT_IMAGE_DATA)); App::Push(std::make_shared()); - log_write("finished app constructor, time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); + log_write("\n\tfinished app constructor, time taken: %.2fs %zums\n\n", ts.GetSecondsD(), ts.GetMs()); } void App::PlaySoundEffect(SoundEffect effect) { @@ -1588,6 +1609,10 @@ void App::DisplayAdvancedOptions(bool left_side) { App::SetReplaceHbmenuEnable(enable); })); + options->Add(std::make_shared("Boost CPU during transfer"_i18n, App::GetApp()->m_progress_boost_mode.Get(), [](bool& enable){ + App::GetApp()->m_progress_boost_mode.Set(enable); + })); + options->Add(std::make_shared("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){ App::SetTextScrollSpeed(index_out); }, App::GetTextScrollSpeed())); @@ -1637,10 +1662,6 @@ void App::DisplayInstallOptions(bool left_side) { App::SetInstallSdEnable(index_out); }, (s64)App::GetInstallSdEnable())); - options->Add(std::make_shared("Boost CPU clock"_i18n, App::GetApp()->m_boost_mode.Get(), [](bool& enable){ - App::GetApp()->m_boost_mode.Set(enable); - })); - options->Add(std::make_shared("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){ App::GetApp()->m_allow_downgrade.Set(enable); })); @@ -1724,6 +1745,9 @@ void App::DisplayDumpOptions(bool left_side) { } App::~App() { + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + log_write("starting to exit\n"); i18n::exit(); diff --git a/sphaira/source/dumper.cpp b/sphaira/source/dumper.cpp index cb56941..61308ed 100644 --- a/sphaira/source/dumper.cpp +++ b/sphaira/source/dumper.cpp @@ -106,13 +106,14 @@ private: Result DumpToFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& root, BaseSource* source, std::span paths) { constexpr s64 BIG_FILE_SIZE = 1024ULL*1024ULL*1024ULL*4ULL; - for (auto path : paths) { + for (const auto& path : paths) { + const auto base_path = fs::AppendPath(root, path); const auto file_size = source->GetSize(path); pbox->SetImage(source->GetIcon(path)); pbox->SetTitle(source->GetName(path)); - pbox->NewTransfer(path); + pbox->NewTransfer(base_path); - const auto temp_path = fs::AppendPath(root, path + ".temp"); + const auto temp_path = base_path + ".temp"; fs->CreateDirectoryRecursivelyWithPath(temp_path); fs->DeleteFile(temp_path); @@ -135,9 +136,8 @@ Result DumpToFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& root, Bas )); } - path = fs::AppendPath(root, path); - fs->DeleteFile(path); - R_TRY(fs->RenameFile(temp_path, path)); + fs->DeleteFile(base_path); + R_TRY(fs->RenameFile(temp_path, base_path)); } R_SUCCEED(); diff --git a/sphaira/source/nro.cpp b/sphaira/source/nro.cpp index 6833d1a..6abde12 100644 --- a/sphaira/source/nro.cpp +++ b/sphaira/source/nro.cpp @@ -56,21 +56,21 @@ auto nro_parse_internal(fs::FsNative& fs, const fs::FsPath& path, NroEntry& entr // R_UNLESS(asset.magic == NROASSETHEADER_MAGIC, NroError_BadMagic); // some .nro (vgedit) have bad nacp, fake the nacp - if (asset.magic != NROASSETHEADER_MAGIC || asset.nacp.offset == 0 || asset.nacp.size != sizeof(entry.nacp)) { + auto& nacp = entry.nacp; + if (asset.magic != NROASSETHEADER_MAGIC || asset.nacp.offset == 0 || asset.nacp.size != sizeof(NacpStruct)) { std::memset(&asset, 0, sizeof(asset)); - std::memset(&entry.nacp, 0, sizeof(entry.nacp)); + std::memset(&nacp, 0, sizeof(nacp)); // get the name without the .nro const auto file_name = std::strrchr(path, '/') + 1; const auto file_name_len = std::strlen(file_name); - for (auto& lang : entry.nacp.lang) { - std::strncpy(lang.name, file_name, file_name_len - 4); - std::strcpy(lang.author, "Unknown"); - } - std::strcpy(entry.nacp.display_version, "Unknown"); + std::strncpy(nacp.lang.name, file_name, file_name_len - 4); + std::strcpy(nacp.lang.author, "Unknown"); + std::strcpy(nacp.display_version, "Unknown"); entry.is_nacp_valid = false; } else { - R_TRY(fsFileRead(&f, data.header.size + asset.nacp.offset, &entry.nacp, sizeof(entry.nacp), FsReadOption_None, &bytes_read)); + R_TRY(fsFileRead(&f, data.header.size + asset.nacp.offset, &nacp.lang, sizeof(nacp.lang), FsReadOption_None, &bytes_read)); + R_TRY(fsFileRead(&f, data.header.size + asset.nacp.offset + offsetof(NacpStruct, display_version), nacp.display_version, sizeof(nacp.display_version), FsReadOption_None, &bytes_read)); entry.is_nacp_valid = true; } diff --git a/sphaira/source/option.cpp b/sphaira/source/option.cpp index 2286c84..6d2a093 100644 --- a/sphaira/source/option.cpp +++ b/sphaira/source/option.cpp @@ -3,7 +3,33 @@ #include "option.hpp" #include "app.hpp" +#include +#include +#include + namespace sphaira::option { +namespace { + +// these are taken from minini in order to parse a value already loaded in memory. +long getl(const char* LocalBuffer, long def) { + const auto len = strlen(LocalBuffer); + return (len == 0) ? def + : ((len >= 2 && toupper((int)LocalBuffer[1]) == 'X') ? strtol(LocalBuffer, NULL, 16) + : strtol(LocalBuffer, NULL, 10)); +} + +bool getbool(const char* LocalBuffer, bool def) { + const auto c = toupper(LocalBuffer[0]); + + if (c == 'Y' || c == '1' || c == 'T') + return true; + else if (c == 'N' || c == '0' || c == 'F') + return false; + else + return def; +} + +} // namespace template auto OptionBase::GetInternal(const char* name) -> T { @@ -47,6 +73,28 @@ void OptionBase::Set(T value) { } } +template +auto OptionBase::LoadFrom(const char* section, const char* name, const char* value) -> bool { + return m_section == section && LoadFrom(name, value); +} + +template +auto OptionBase::LoadFrom(const char* name, const char* value) -> bool { + if (m_name == name) { + if constexpr(std::is_same_v) { + m_value = getbool(value, m_default_value); + } else if constexpr(std::is_same_v) { + m_value = getl(value, m_default_value); + } else if constexpr(std::is_same_v) { + m_value = value; + } + + return true; + } + + return false; +} + template struct OptionBase; template struct OptionBase; template struct OptionBase; diff --git a/sphaira/source/owo.cpp b/sphaira/source/owo.cpp index 4a0d40d..597f613 100644 --- a/sphaira/source/owo.cpp +++ b/sphaira/source/owo.cpp @@ -34,6 +34,14 @@ constexpr u32 PFS0_PADDING_SIZE = 0x200; constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200; +constexpr const u8 HBL_MAIN_DATA[]{ + #embed +}; + +constexpr const u8 HBL_NPDM_DATA[]{ + #embed +}; + // stdio-like wrapper for std::vector struct BufHelper { BufHelper() = default; @@ -831,6 +839,9 @@ auto create_meta_nca(u64 tid, const keys::Keys& keys, NcmStorageId storage_id, c } auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStorageId storage_id) -> Result { + pbox->SetTitle(config.name); + pbox->SetImageDataConst(config.icon); + R_UNLESS(!config.nro_path.empty(), OwoError_BadArgs); // R_UNLESS(!config.icon.empty(), OwoError_BadArgs); @@ -864,13 +875,10 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor // create program if (config.program_nca.empty()) { - R_UNLESS(!config.main.empty(), OwoError_BadArgs); - R_UNLESS(!config.npdm.empty(), OwoError_BadArgs); - pbox->NewTransfer("Creating Program"_i18n).UpdateTransfer(0, 8); FileEntries exefs; - add_file_entry(exefs, "main", config.main); - add_file_entry(exefs, "main.npdm", config.npdm); + add_file_entry(exefs, "main", HBL_MAIN_DATA); + add_file_entry(exefs, "main.npdm", HBL_NPDM_DATA); FileEntries romfs; add_file_entry(romfs, "/nextArgv", config.args.data(), config.args.length()); diff --git a/sphaira/source/ui/menus/appstore.cpp b/sphaira/source/ui/menus/appstore.cpp index fea1e1a..70a8e6d 100644 --- a/sphaira/source/ui/menus/appstore.cpp +++ b/sphaira/source/ui/menus/appstore.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,22 @@ constexpr auto URL_JSON = "https://switch.cdn.fortheusers.org/repo.json"; constexpr auto URL_POST_FEEDBACK = "http://switchbru.com/appstore/feedback"; constexpr auto URL_GET_FEEDACK = "http://switchbru.com/appstore/feedback"; +constexpr const u8 UPDATE_IMAGE_DATA[]{ + #embed +}; + +constexpr const u8 GET_IMAGE_DATA[]{ + #embed +}; + +constexpr const u8 LOCAL_IMAGE_DATA[]{ + #embed +}; + +constexpr const u8 INSTALLED_IMAGE_DATA[]{ + #embed +}; + constexpr const char* FILTER_STR[] = { "All", "Games", @@ -172,7 +189,7 @@ auto LoadAndParseManifest(const Entry& e) -> ManifestEntries { return ParseManifest(std::span{(const char*)data.data(), data.size()}); } -auto EntryLoadImageFile(fs::Fs& fs, const fs::FsPath& path, LazyImage& image) -> bool { +auto EntryLoadImageData(std::span image_buf, LazyImage& image) -> bool { // already have the image if (image.image) { // log_write("warning, tried to load image: %s when already loaded\n", path); @@ -180,17 +197,29 @@ auto EntryLoadImageFile(fs::Fs& fs, const fs::FsPath& path, LazyImage& image) -> } auto vg = App::GetVg(); + int channels_in_file; + auto buf = stbi_load_from_memory(image_buf.data(), image_buf.size(), &image.w, &image.h, &channels_in_file, 4); + if (buf) { + ON_SCOPE_EXIT(stbi_image_free(buf)); + std::memcpy(image.first_pixel, buf, sizeof(image.first_pixel)); + image.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, buf); + } + + return image.image; +} + +auto EntryLoadImageFile(fs::Fs& fs, const fs::FsPath& path, LazyImage& image) -> bool { + // already have the image + if (image.image) { + // log_write("warning, tried to load image: %s when already loaded\n", path); + return true; + } + std::vector image_buf; if (R_FAILED(fs.read_entire_file(path, image_buf))) { log_write("failed to load image from file: %s\n", path.s); } else { - int channels_in_file; - auto buf = stbi_load_from_memory(image_buf.data(), image_buf.size(), &image.w, &image.h, &channels_in_file, 4); - if (buf) { - ON_SCOPE_EXIT(stbi_image_free(buf)); - std::memcpy(image.first_pixel, buf, sizeof(image.first_pixel)); - image.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, buf); - } + EntryLoadImageData(image_buf, image); } if (!image.image) { @@ -311,7 +340,7 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> Result { // remove directory, this will also delete manifest and info const auto dir = BuildPackageCachePath(entry); - pbox->NewTransfer("Removing "_i18n + dir); + pbox->NewTransfer("Removing "_i18n + dir.toString()); if (R_FAILED(fs.DeleteDirectoryRecursively(dir))) { log_write("failed to delete folder: %s\n", dir.s); } else { @@ -329,7 +358,7 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> Result { // 4. move everything from placeholder to normal location auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result { static const fs::FsPath zip_out{"/switch/sphaira/cache/appstore/temp.zip"}; - constexpr auto chunk_size = 1024 * 512; // 512KiB + std::vector buf(1024 * 512); // 512KiB fs::FsNativeSd fs; R_TRY(fs.GetFsOpenResult()); @@ -405,26 +434,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result { } } - const auto unzip_to = [pbox, &fs, zfile](const fs::FsPath& inzip, fs::FsPath output) -> Result { - pbox->NewTransfer(inzip); - - if (UNZ_END_OF_LIST_OF_FILE == unzLocateFile(zfile, inzip, 0)) { - log_write("failed to find %s\n", inzip.s); - R_THROW(0x1); - } - - if (UNZ_OK != unzOpenCurrentFile(zfile)) { - log_write("failed to open current file\n"); - R_THROW(0x1); - } - ON_SCOPE_EXIT(unzCloseCurrentFile(zfile)); - - unz_file_info64 info; - if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, 0, 0, 0, 0, 0, 0)) { - log_write("failed to get current info\n"); - R_THROW(0x1); - } - + const auto unzip_to_file = [&](const unz_file_info64& info, const fs::FsPath& inzip, fs::FsPath output) -> Result { if (output[0] != '/') { output = fs::AppendPath("/", output); } @@ -444,7 +454,6 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result { R_TRY(fsFileSetSize(&f, info.uncompressed_size)); - std::vector buf(chunk_size); u64 offset{}; while (offset < info.uncompressed_size) { R_TRY(pbox->ShouldExitResult()); @@ -464,37 +473,106 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result { R_SUCCEED(); }; - // unzip manifest and info - R_TRY(unzip_to("info.json", BuildInfoCachePath(entry))); - R_TRY(unzip_to("manifest.install", BuildManifestCachePath(entry))); + const auto unzip_to = [&](const fs::FsPath& inzip, const fs::FsPath& output) -> Result { + pbox->NewTransfer(inzip); - for (auto& new_entry : new_manifest) { - R_TRY(pbox->ShouldExitResult()); - - switch (new_entry.command) { - case 'E': // both are the same? - case 'U': - break; - - case 'G': { // checks if file exists, if not, extract - if (fs.FileExists(fs::AppendPath("/", new_entry.path))) { - continue; - } - } break; - - default: - log_write("bad command: %c\n", new_entry.command); - continue; + if (UNZ_END_OF_LIST_OF_FILE == unzLocateFile(zfile, inzip, 0)) { + log_write("failed to find %s\n", inzip.s); + R_THROW(0x1); } - R_TRY(unzip_to(new_entry.path, new_entry.path)); - } + if (UNZ_OK != unzOpenCurrentFile(zfile)) { + log_write("failed to open current file\n"); + R_THROW(0x1); + } + ON_SCOPE_EXIT(unzCloseCurrentFile(zfile)); + + unz_file_info64 info; + if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, 0, 0, 0, 0, 0, 0)) { + log_write("failed to get current info\n"); + R_THROW(0x1); + } + + return unzip_to_file(info, inzip, output); + }; + + const auto unzip_all = [&](std::span entries) -> Result { + unz_global_info64 ginfo; + if (UNZ_OK != unzGetGlobalInfo64(zfile, &ginfo)) { + R_THROW(0x1); + } + + if (UNZ_OK != unzGoToFirstFile(zfile)) { + R_THROW(0x1); + } + + for (s64 i = 0; i < ginfo.number_entry; i++) { + R_TRY(pbox->ShouldExitResult()); + + if (i > 0) { + if (UNZ_OK != unzGoToNextFile(zfile)) { + log_write("failed to unzGoToNextFile\n"); + R_THROW(0x1); + } + } + + if (UNZ_OK != unzOpenCurrentFile(zfile)) { + log_write("failed to open current file\n"); + R_THROW(0x1); + } + ON_SCOPE_EXIT(unzCloseCurrentFile(zfile)); + + unz_file_info64 info; + char name[512]; + if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, name, sizeof(name), 0, 0, 0, 0)) { + log_write("failed to get current info\n"); + R_THROW(0x1); + } + + const auto it = std::ranges::find_if(entries, [&name](auto& e){ + return !strcasecmp(name, e.path); + }); + + if (it == entries.end()) [[unlikely]] { + continue; + } + + pbox->NewTransfer(it->path); + + switch (it->command) { + case 'E': // both are the same? + case 'U': + break; + + case 'G': { // checks if file exists, if not, extract + if (fs.FileExists(fs::AppendPath("/", it->path))) { + continue; + } + } break; + + default: + log_write("bad command: %c\n", it->command); + continue; + } + + R_TRY(unzip_to_file(info, it->path, it->path)); + } + + R_SUCCEED(); + }; + + // unzip manifest, info and all entries. + TimeStamp ts; + R_TRY(unzip_to("info.json", BuildInfoCachePath(entry))); + R_TRY(unzip_to("manifest.install", BuildManifestCachePath(entry))); + R_TRY(unzip_all(new_manifest)); + log_write("\n\t[APPSTORE] finished extract new, time taken: %.2fs %zums\n\n", ts.GetSecondsD(), ts.GetMs()); // finally finally, remove files no longer in the manifest for (auto& old_entry : old_manifest) { bool found = false; for (auto& new_entry : new_manifest) { - if (!std::strcmp(old_entry.path, new_entry.path)) { + if (!strcasecmp(old_entry.path, new_entry.path)) { found = true; break; } @@ -1022,14 +1100,11 @@ void Menu::OnFocusGained() { // log_write("saying we got focus base: size: %zu count: %zu\n", repo_json.size(), m_entries.size()); if (!m_default_image.image) { - if (R_SUCCEEDED(romfsInit())) { - ON_SCOPE_EXIT(romfsExit()); - EntryLoadImageFile("romfs:/default.png", m_default_image); - EntryLoadImageFile("romfs:/UPDATE.png", m_update); - EntryLoadImageFile("romfs:/GET.png", m_get); - EntryLoadImageFile("romfs:/LOCAL.png", m_local); - EntryLoadImageFile("romfs:/INSTALLED.png", m_installed); - } + EntryLoadImageData(App::GetDefaultImageData(), m_default_image); + EntryLoadImageData(UPDATE_IMAGE_DATA, m_update); + EntryLoadImageData(GET_IMAGE_DATA, m_get); + EntryLoadImageData(LOCAL_IMAGE_DATA, m_local); + EntryLoadImageData(INSTALLED_IMAGE_DATA, m_installed); } if (m_entries.empty()) { @@ -1074,6 +1149,9 @@ void Menu::SetIndex(s64 index) { } void Menu::ScanHomebrew() { + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + from_json(REPO_PATH, m_entries); fs::FsNativeSd fs; diff --git a/sphaira/source/ui/menus/filebrowser.cpp b/sphaira/source/ui/menus/filebrowser.cpp index b4765d5..8961a2c 100644 --- a/sphaira/source/ui/menus/filebrowser.cpp +++ b/sphaira/source/ui/menus/filebrowser.cpp @@ -941,6 +941,9 @@ void FsView::InstallForwarder() { 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(); @@ -955,9 +958,9 @@ void FsView::InstallForwarder() { OwoConfig config{}; config.nro_path = assoc.path.toString(); config.args = nro_add_arg_file(GetNewPathCurrent()); - config.name = nro.nacp.lang[0].name + std::string{" | "} + file_name; + config.name = nro.nacp.lang.name + std::string{" | "} + file_name; // config.name = file_name; - config.nacp = nro.nacp; + config.nacp = nacp; config.icon = GetRomIcon(m_fs.get(), pbox, file_name, db_indexs, nro); pbox->SetImageDataConst(config.icon); @@ -1017,7 +1020,7 @@ void FsView::UnzipFiles(fs::FsPath dir_path) { R_THROW(0x1); } - for (int i = 0; i < pglobal_info.number_entry; i++) { + for (s64 i = 0; i < pglobal_info.number_entry; i++) { if (i > 0) { if (UNZ_OK != unzGoToNextFile(zfile)) { log_write("failed to unzGoToNextFile\n"); @@ -1325,6 +1328,9 @@ void FsView::UploadFiles() { } auto FsView::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result { + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + log_write("new scan path: %s\n", new_path.s); if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) { const LastFile f(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size()); @@ -1781,7 +1787,7 @@ static Result DeleteAllCollections(ProgressBox* pbox, fs::Fs* fs, const Selected const auto full_path = FsView::GetNewPath(c.path, p.name); pbox->SetTitle(p.name); - pbox->NewTransfer("Deleting "_i18n + full_path); + pbox->NewTransfer("Deleting "_i18n + full_path.toString()); if ((mode & FsDirOpenMode_ReadDirs) && p.type == FsDirEntryType_Dir) { log_write("deleting dir: %s\n", full_path.s); R_TRY(fs->DeleteDirectory(full_path)); @@ -1804,7 +1810,7 @@ static Result DeleteAllCollections(ProgressBox* pbox, fs::Fs* fs, const Selected const auto full_path = FsView::GetNewPath(selected.m_path, p.name); pbox->SetTitle(p.name); - pbox->NewTransfer("Deleting "_i18n + full_path); + pbox->NewTransfer("Deleting "_i18n + full_path.toString()); if ((mode & FsDirOpenMode_ReadDirs) && p.type == FsDirEntryType_Dir) { log_write("deleting dir: %s\n", full_path.s); diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index fa6dbc2..f9aedf7 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -981,6 +981,9 @@ void Menu::ScanHomebrew() { const auto hide_forwarders = m_hide_forwarders.Get(); TimeStamp ts; + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + FreeEntries(); m_entries.reserve(ENTRY_CHUNK_COUNT); diff --git a/sphaira/source/ui/menus/gc_menu.cpp b/sphaira/source/ui/menus/gc_menu.cpp index 846dc57..6bb4609 100644 --- a/sphaira/source/ui/menus/gc_menu.cpp +++ b/sphaira/source/ui/menus/gc_menu.cpp @@ -953,6 +953,9 @@ void Menu::OnChangeIndex(s64 new_index) { } Result Menu::DumpGames(u32 flags) { + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + R_TRY(GcMountStorage()); u32 location_flags = dump::DumpLocationFlag_All; diff --git a/sphaira/source/ui/menus/homebrew.cpp b/sphaira/source/ui/menus/homebrew.cpp index 216f355..4008c59 100644 --- a/sphaira/source/ui/menus/homebrew.cpp +++ b/sphaira/source/ui/menus/homebrew.cpp @@ -233,7 +233,7 @@ void Menu::SetIndex(s64 index) { void Menu::InstallHomebrew() { const auto& nro = m_entries[m_index]; - InstallHomebrew(nro.path, nro.nacp, nro_get_icon(nro.path, nro.icon_size, nro.icon_offset)); + InstallHomebrew(nro.path, nro_get_icon(nro.path, nro.icon_size, nro.icon_offset)); } void Menu::ScanHomebrew() { @@ -266,8 +266,6 @@ void Menu::ScanHomebrew() { if (user->ini) { if (!strcmp(Key, "timestamp")) { user->ini->timestamp = atoi(Value); - } else if (!strcmp(Key, "launch_count")) { - user->ini->launch_count = atoi(Value); } } @@ -412,19 +410,16 @@ void Menu::OnLayoutChange() { grid::Menu::OnLayoutChange(m_list, m_layout.Get()); } -Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector& icon) { +Result Menu::InstallHomebrew(const fs::FsPath& path, const std::vector& icon) { OwoConfig config{}; config.nro_path = path.toString(); - config.nacp = nacp; + R_TRY(nro_get_nacp(path, config.nacp)); config.icon = icon; return App::Install(config); } Result Menu::InstallHomebrewFromPath(const fs::FsPath& path) { - NacpStruct nacp; - R_TRY(nro_get_nacp(path, nacp)) - const auto icon = nro_get_icon(path); - return InstallHomebrew(path, nacp, icon); + return InstallHomebrew(path, nro_get_icon(path)); } } // namespace sphaira::ui::menu::homebrew diff --git a/sphaira/source/ui/menus/themezer.cpp b/sphaira/source/ui/menus/themezer.cpp index 5d3385c..20f4e59 100644 --- a/sphaira/source/ui/menus/themezer.cpp +++ b/sphaira/source/ui/menus/themezer.cpp @@ -647,6 +647,9 @@ void Menu::PackListDownload() { curl::Flags{curl::Flag_Cache}, curl::StopToken{this->GetToken()}, curl::OnComplete{[this, page_index](auto& result){ + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); + log_write("got themezer data\n"); if (!result.success) { auto& page = m_pages[page_index-1]; diff --git a/sphaira/source/ui/progress_box.cpp b/sphaira/source/ui/progress_box.cpp index 2fd68d9..71ff1eb 100644 --- a/sphaira/source/ui/progress_box.cpp +++ b/sphaira/source/ui/progress_box.cpp @@ -19,6 +19,10 @@ void threadFunc(void* arg) { } // namespace ProgressBox::ProgressBox(int image, const std::string& action, const std::string& title, ProgressBoxCallback callback, ProgressBoxDoneCallback done, int cpuid, int prio, int stack_size) { + if (App::GetApp()->m_progress_boost_mode.Get()) { + appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); + } + SetAction(Button::B, Action{"Back"_i18n, [this](){ App::Push(std::make_shared("Are you sure you wish to cancel?"_i18n, "No"_i18n, "Yes"_i18n, 1, [this](auto op_index){ if (op_index && *op_index) { @@ -61,6 +65,8 @@ ProgressBox::~ProgressBox() { FreeImage(); m_done(m_thread_data.result); + + appletSetCpuBoostMode(ApmCpuBoostMode_Normal); } auto ProgressBox::Update(Controller* controller, TouchInfo* touch) -> void { diff --git a/sphaira/source/yati/yati.cpp b/sphaira/source/yati/yati.cpp index d8f27d8..02d0175 100644 --- a/sphaira/source/yati/yati.cpp +++ b/sphaira/source/yati/yati.cpp @@ -767,16 +767,11 @@ Yati::~Yati() { ncmContentStorageClose(std::addressof(ncm_cs[i])); } - if (config.boost_mode) { - appletSetCpuBoostMode(ApmCpuBoostMode_Normal); - } - App::SetAutoSleepDisabled(false); } Result Yati::Setup(const ConfigOverride& override) { config.sd_card_install = override.sd_card_install.value_or(App::GetApp()->m_install_sd.Get()); - config.boost_mode = App::GetApp()->m_boost_mode.Get(); config.allow_downgrade = App::GetApp()->m_allow_downgrade.Get(); config.skip_if_already_installed = App::GetApp()->m_skip_if_already_installed.Get(); config.ticket_only = App::GetApp()->m_ticket_only.Get(); @@ -794,10 +789,6 @@ Result Yati::Setup(const ConfigOverride& override) { config.lower_system_version = override.lower_system_version.value_or(App::GetApp()->m_lower_system_version.Get()); storage_id = config.sd_card_install ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser; - if (config.boost_mode) { - appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); - } - R_TRY(source->GetOpenResult()); R_TRY(splCryptoInitialize()); R_TRY(nsInitialize());