diff --git a/Makefile b/Makefile index 031f7979..5061c84f 100644 --- a/Makefile +++ b/Makefile @@ -42,12 +42,14 @@ uloader: umenu: pu @$(MAKE) -C projects/uMenu @mkdir -p SdOut/ulaunch/bin/uMenu + @mkdir -p SdOut/ulaunch/lang/uMenu @cp projects/uMenu/uMenu.nso SdOut/ulaunch/bin/uMenu/main @cp projects/uMenu/uMenu.npdm SdOut/ulaunch/bin/uMenu/main.npdm @build_romfs projects/uMenu/romfs SdOut/ulaunch/bin/uMenu/romfs.bin umanager: pu @$(MAKE) -C projects/uManager + @mkdir -p SdOut/ulaunch/lang/uManager @mkdir -p SdOut/switch @cp projects/uManager/uManager.nro SdOut/switch/uManager.nro diff --git a/README.md b/README.md index 7561bec7..a315e2bc 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Table of contents - Browse the Internet (via the normally hidden web-applet) directly from the main menu! - - Toggle between uLaunch and the original HOME menu (no permanent removal) using our `uManager` homebrew tool! + - Toggle between uLaunch and the original HOME menu (no permanent removal) and/or easily update uLaunch using our `uManager` homebrew tool! - Stream the screen via USB (although at low speeds, about ~9 FPS) via `uScreen`! (can be useful for taking quick screenshots, specially since uLaunch is able to capture more than SysDVR or usual game capture) diff --git a/libs/uCommon/include/ul/cfg/cfg_Config.hpp b/libs/uCommon/include/ul/cfg/cfg_Config.hpp index e80e1c04..a4ee99d0 100644 --- a/libs/uCommon/include/ul/cfg/cfg_Config.hpp +++ b/libs/uCommon/include/ul/cfg/cfg_Config.hpp @@ -283,11 +283,15 @@ namespace ul::cfg { std::vector LoadThemes(); std::string GetAssetByTheme(const Theme &base, const std::string &resource_base); - inline std::string GetLanguageJSONPath(const std::string &lang) { - return fs::JoinPath(LanguagesPath, lang + ".json"); - } + void LoadLanguageJsons(const std::string &lang_base, util::JSON &lang, util::JSON &def); - std::string GetLanguageString(const util::JSON &lang, const util::JSON &def, const std::string &name); + inline std::string GetLanguageString(const util::JSON &lang, const util::JSON &def, const std::string &name) { + auto str = lang.value(name, ""); + if(str.empty()) { + str = def.value(name, ""); + } + return str; + } Config CreateNewAndLoadConfig(); Config LoadConfig(); diff --git a/libs/uCommon/include/ul/ul_Include.hpp b/libs/uCommon/include/ul/ul_Include.hpp index 62c24305..4d1b0620 100644 --- a/libs/uCommon/include/ul/ul_Include.hpp +++ b/libs/uCommon/include/ul/ul_Include.hpp @@ -9,7 +9,8 @@ namespace ul { constexpr const char DefaultThemePath[] = "romfs:/default"; - constexpr const char DefaultLanguagePath[] = "romfs:/en.json"; + constexpr const char DefaultLanguagePath[] = "romfs:/lang"; + constexpr const char DefaultLanguage[] = "en-US"; constexpr const char RootPath[] = "sdmc:/ulaunch"; @@ -18,14 +19,15 @@ namespace ul { constexpr const char ThemesPath[] = "sdmc:/ulaunch/themes"; constexpr const char MenuPath[] = "sdmc:/ulaunch/menu"; - - constexpr const char LanguagesPath[] = "sdmc:/ulaunch/lang"; + constexpr const char MenuLanguagesPath[] = "sdmc:/ulaunch/lang/uMenu"; constexpr const char RootCachePath[] = "sdmc:/ulaunch/cache"; constexpr const char ApplicationCachePath[] = "sdmc:/ulaunch/cache/app"; constexpr const char HomebrewCachePath[] = "sdmc:/ulaunch/cache/hb"; constexpr const char AccountCachePath[] = "sdmc:/ulaunch/cache/acc"; + constexpr const char ManagerLanguagesPath[] = "sdmc:/ulaunch/lang/uManager"; + constexpr const char OldMenuPath[] = "sdmc:/ulaunch/entries"; constexpr const char OldApplicationCachePath[] = "sdmc:/ulaunch/titles"; constexpr const char OldHomebrewCachePath[] = "sdmc:/ulaunch/nro"; diff --git a/libs/uCommon/source/ul/cfg/cfg_Config.cpp b/libs/uCommon/source/ul/cfg/cfg_Config.cpp index e0591277..65c9a873 100644 --- a/libs/uCommon/source/ul/cfg/cfg_Config.cpp +++ b/libs/uCommon/source/ul/cfg/cfg_Config.cpp @@ -57,12 +57,20 @@ namespace ul::cfg { return ""; } - std::string GetLanguageString(const util::JSON &lang, const util::JSON &def, const std::string &name) { - auto str = lang.value(name, ""); - if(str.empty()) { - str = def.value(name, ""); + void LoadLanguageJsons(const std::string &lang_base, util::JSON &lang, util::JSON &def) { + const auto default_lang_file_path = fs::JoinPath(DefaultLanguagePath, DefaultLanguage) + ".json"; + UL_RC_ASSERT(ul::util::LoadJSONFromFile(def, default_lang_file_path)); + + u64 lang_code = 0; + UL_RC_ASSERT(setGetLanguageCode(&lang_code)); + + const auto ext_lang_path = fs::JoinPath(lang_base, reinterpret_cast(&lang_code)) + ".json"; + if(R_FAILED(util::LoadJSONFromFile(lang, ext_lang_path))) { + const auto lang_path = fs::JoinPath(DefaultLanguagePath, reinterpret_cast(&lang_code)) + ".json"; + if(R_FAILED(util::LoadJSONFromFile(lang, lang_path))) { + lang = def; + } } - return str; } Config CreateNewAndLoadConfig() { diff --git a/libs/uCommon/source/ul/menu/menu_Entries.cpp b/libs/uCommon/source/ul/menu/menu_Entries.cpp index 5b1dae9b..71701b38 100644 --- a/libs/uCommon/source/ul/menu/menu_Entries.cpp +++ b/libs/uCommon/source/ul/menu/menu_Entries.cpp @@ -111,24 +111,9 @@ namespace ul::menu { void InitializeRemainingEntries(const std::vector &remaining_apps) { const std::vector DefaultHomebrewRecordPaths = { HbmenuPath, ManagerPath }; - // Reserve for all remaining apps + special homebrews - const auto index_gap = UINT32_MAX / (remaining_apps.size() + DefaultHomebrewRecordPaths.size()); - - // Add remaining app entries + // Reserve for special homebrews + all remaining apps + const auto index_gap = UINT32_MAX / (DefaultHomebrewRecordPaths.size() + remaining_apps.size()); u32 cur_start_idx = 0; - for(const auto &app_record : remaining_apps) { - Entry app_entry = { - .type = EntryType::Application, - .entry_path = MakeEntryPath(MenuPath, cur_start_idx + index_gap / 2), - - .app_info = { - .app_id = app_record.application_id, - .record = app_record - } - }; - app_entry.Save(); - cur_start_idx += index_gap; - } // Add special homebrew entries for(const auto &nro_path : DefaultHomebrewRecordPaths) { @@ -143,6 +128,21 @@ namespace ul::menu { hb_entry.Save(); cur_start_idx += index_gap; } + + // Add remaining app entries + for(const auto &app_record : remaining_apps) { + Entry app_entry = { + .type = EntryType::Application, + .entry_path = MakeEntryPath(MenuPath, cur_start_idx + index_gap / 2), + + .app_info = { + .app_id = app_record.application_id, + .record = app_record + } + }; + app_entry.Save(); + cur_start_idx += index_gap; + } } void ConvertOldMenu() { diff --git a/projects/uManager/Makefile b/projects/uManager/Makefile index bded5d35..bdbc3e62 100644 --- a/projects/uManager/Makefile +++ b/projects/uManager/Makefile @@ -42,7 +42,7 @@ BUILD := build SOURCES := source source/ul source/ul/man source/ul/man/ui INCLUDES := include DATA := data -# ROMFS := romfs +ROMFS := romfs VER_MAJOR := 1 VER_MINOR := 0 @@ -71,7 +71,7 @@ LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $* LIBUCOMMON := $(CURDIR)/../../libs/uCommon LIBPU := $(CURDIR)/../../libs/Plutonium/Plutonium -LIBS := -lnx -lpu -lfreetype -lSDL2_mixer -lopusfile -lopus -lmodplug -lmpg123 -lvorbisidec -lc -logg -lSDL2_ttf -lSDL2_gfx -lSDL2_image -lwebp -lpng -ljpeg `sdl2-config --libs` `freetype-config --libs` +LIBS := -lpu -lzzip -lcurl -lfreetype -lSDL2_mixer -lopusfile -lopus -lmodplug -lmpg123 -lvorbisidec -lc -logg -lSDL2_ttf -lSDL2_gfx -lSDL2_image -lwebp -lpng -ljpeg `sdl2-config --libs` `freetype-config --libs` -luCommon -lnx #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/projects/uManager/include/ul/man/man_Manager.hpp b/projects/uManager/include/ul/man/man_Manager.hpp index ff1cd286..181a9659 100644 --- a/projects/uManager/include/ul/man/man_Manager.hpp +++ b/projects/uManager/include/ul/man/man_Manager.hpp @@ -1,5 +1,6 @@ #pragma once +#include
    namespace ul::man { @@ -12,4 +13,43 @@ namespace ul::man { void ActivateSystem(); void DeactivateSystem(); + struct Version { + u32 major; + u32 minor; + s32 micro; + + std::string AsString() const; + + static inline constexpr Version MakeVersion(const u32 major, const u32 minor, const u32 micro) { + return { major, minor, static_cast(micro) }; + } + + static Version FromString(const std::string &ver_str); + + inline constexpr bool IsLower(const Version &other) const { + if(this->major > other.major) { + return true; + } + else if(this->major == other.major) { + if(this->minor > other.minor) { + return true; + } + else if(this->minor == other.minor) { + if(this->micro > other.micro) { + return true; + } + } + } + return false; + } + + inline constexpr bool IsHigher(const Version &other) const { + return !this->IsLower(other) && !this->IsEqual(other); + } + + inline constexpr bool IsEqual(const Version &other) const { + return ((this->major == other.major) && (this->minor == other.minor) && (this->micro == other.micro)); + } + }; + } \ No newline at end of file diff --git a/projects/uManager/include/ul/man/man_Network.hpp b/projects/uManager/include/ul/man/man_Network.hpp new file mode 100644 index 00000000..3e88971f --- /dev/null +++ b/projects/uManager/include/ul/man/man_Network.hpp @@ -0,0 +1,16 @@ + +#pragma once +#include
      +#include + +namespace ul::man { + + constexpr const char TemporaryReleaseZipPath[] = "sdmc:/ulaunch_tmp.zip"; + + using RetrieveOnProgressCallback = std::function; + + std::string RetrieveContent(const std::string &url, const std::string &mime_type = ""); + void RetrieveToFile(const std::string &url, const std::string &path, RetrieveOnProgressCallback on_progress_cb); + bool HasConnection(); + +} \ No newline at end of file diff --git a/projects/uManager/include/ul/man/ui/ui_MainMenuLayout.hpp b/projects/uManager/include/ul/man/ui/ui_MainMenuLayout.hpp index 62cd5248..f84c63f4 100644 --- a/projects/uManager/include/ul/man/ui/ui_MainMenuLayout.hpp +++ b/projects/uManager/include/ul/man/ui/ui_MainMenuLayout.hpp @@ -9,14 +9,22 @@ namespace ul::man::ui { static constexpr u32 MenuItemCount = 6; private: - pu::ui::elm::Menu::Ref options_menu; pu::ui::elm::TextBlock::Ref info_text; + + pu::ui::elm::Menu::Ref options_menu; pu::ui::elm::MenuItem::Ref activate_menu_item; + pu::ui::elm::MenuItem::Ref update_menu_item; + + inline void ResetInfoText() { + this->info_text->SetText("uManager v" UL_VERSION " - uLaunch's manager"); + } + public: MainMenuLayout(); PU_SMART_CTOR(MainMenuLayout) void activate_DefaultKey(); + void update_DefaultKey(); }; } \ No newline at end of file diff --git a/projects/uManager/romfs/lang/en-US.json b/projects/uManager/romfs/lang/en-US.json new file mode 100644 index 00000000..38993484 --- /dev/null +++ b/projects/uManager/romfs/lang/en-US.json @@ -0,0 +1,24 @@ +{ + "yes": "Yes", + "cancel": "Cancel", + "ok": "Ok", + "reboot": "Reboot", + "status": "Status", + "status_active": "active", + "status_not_active": "not active", + "status_not_present": "not present", + "update_item": "Check for updates", + "activate_changes_title": "Changes", + "activate_changes": "The (de)activation of uLaunch needs a reboot for changes to take effect.", + "activate_continue": "Continue", + "activate_not_present": "uLaunch was not found in the SD card.", + "update_title": "Update check", + "update_error": "Unable to retrieve last update information...", + "update_version": "Last release", + "update_conf": "Would you like to update to the last release?", + "update_equal": "uLaunch is already on the latest release.", + "update_higher": "uLaunch seems to be in a newer version than the latest release...", + "update_success": "uLaunch was successfully updated, now the system must reboot for the update to apply.", + "update_progress_download": "Downloading update...", + "update_progress_install": "Installing update..." +} \ No newline at end of file diff --git a/projects/uManager/source/main.cpp b/projects/uManager/source/main.cpp index 6787c51a..a75f9e25 100644 --- a/projects/uManager/source/main.cpp +++ b/projects/uManager/source/main.cpp @@ -1,8 +1,11 @@ #include
        +#include
          ul::man::ui::MainApplication::Ref g_MainApplication; int main() { + ul::InitializeLogging("uManager"); + auto renderer_opts = pu::ui::render::RendererInitOptions(SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags); renderer_opts.UseImage(pu::ui::render::IMGAllFlags); renderer_opts.UseTTF(); diff --git a/projects/uManager/source/ul/man/man_Manager.cpp b/projects/uManager/source/ul/man/man_Manager.cpp index 2da86baf..5e04046f 100644 --- a/projects/uManager/source/ul/man/man_Manager.cpp +++ b/projects/uManager/source/ul/man/man_Manager.cpp @@ -68,4 +68,46 @@ namespace ul::man { fsdevDeleteDirectoryRecursively(ActiveSystemPath); } + std::string Version::AsString() const { + auto as_str = std::to_string(this->major) + "." + std::to_string(this->minor); + if(this->micro > 0) { + as_str += "." + std::to_string(this->micro); + } + return as_str; + } + + Version Version::FromString(const std::string &ver_str) { + auto ver_str_cpy = ver_str; + Version v = {}; + size_t pos = 0; + std::string token; + u32 c = 0; + std::string delimiter = "."; + while((pos = ver_str_cpy.find(delimiter)) != std::string::npos) { + token = ver_str_cpy.substr(0, pos); + if(c == 0) { + v.major = std::stoi(token); + } + else if(c == 1) { + v.minor = std::stoi(token); + } + else if(c == 2) { + v.micro = std::stoi(token); + } + ver_str_cpy.erase(0, pos + delimiter.length()); + c++; + } + + if(c == 0) { + v.major = std::stoi(ver_str_cpy); + } + else if(c == 1) { + v.minor = std::stoi(ver_str_cpy); + } + else if(c == 2) { + v.micro = std::stoi(ver_str_cpy); + } + return v; + } + } \ No newline at end of file diff --git a/projects/uManager/source/ul/man/man_Network.cpp b/projects/uManager/source/ul/man/man_Network.cpp new file mode 100644 index 00000000..f4d20114 --- /dev/null +++ b/projects/uManager/source/ul/man/man_Network.cpp @@ -0,0 +1,90 @@ +#include
            +#include +#include + +namespace ul::man { + + namespace { + + std::size_t StringWriteImpl(const char* in, std::size_t size, std::size_t count, std::string *out) { + const auto total_size = size * count; + out->append(in, total_size); + return total_size; + } + + std::size_t FileWriteImpl(const char* in, std::size_t size, std::size_t count, FILE *out) { + fwrite(in, size, count, out); + return size * count; + } + + void DummyOnProgressCallback(const double now_downloaded, const double total_to_download) { + } + + RetrieveOnProgressCallback g_CurrentOnProgressCallback = DummyOnProgressCallback; + + int ProgressImpl(void *_ptr, double total_to_download, double now_downloaded, double _total_to_upload, double _now_uploaded) { + g_CurrentOnProgressCallback(now_downloaded, total_to_download); + return 0; + } + + } + + std::string RetrieveContent(const std::string &url, const std::string &mime_type) { + const auto rc = socketInitializeDefault(); + if(R_FAILED(rc)) { + return ""; + } + + std::string content; + auto curl = curl_easy_init(); + if(!mime_type.empty()) { + curl_slist *header_data = nullptr; + header_data = curl_slist_append(header_data, ("Content-Type: " + mime_type).c_str()); + header_data = curl_slist_append(header_data, ("Accept: " + mime_type).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_data); + } + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "uManager"); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0l); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0l); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1l); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, StringWriteImpl); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &content); + curl_easy_perform(curl); + curl_easy_cleanup(curl); + socketExit(); + return content; + } + + void RetrieveToFile(const std::string &url, const std::string &path, RetrieveOnProgressCallback on_progress_cb) { + const auto rc = socketInitializeDefault(); + if(R_FAILED(rc)) { + return; + } + + auto f = fopen(path.c_str(), "wb"); + if(f) { + g_CurrentOnProgressCallback = on_progress_cb; + auto curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "uManager"); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0l); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0l); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1l); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, FileWriteImpl); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0l); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, ProgressImpl); + + curl_easy_perform(curl); + curl_easy_cleanup(curl); + fclose(f); + } + socketExit(); + } + + bool HasConnection() { + return gethostid() == INADDR_LOOPBACK; + } + +} \ No newline at end of file diff --git a/projects/uManager/source/ul/man/ui/ui_MainApplication.cpp b/projects/uManager/source/ul/man/ui/ui_MainApplication.cpp index d0299568..16a020c4 100644 --- a/projects/uManager/source/ul/man/ui/ui_MainApplication.cpp +++ b/projects/uManager/source/ul/man/ui/ui_MainApplication.cpp @@ -1,10 +1,18 @@ #include
              +#include
                +#include
                  extern ul::man::ui::MainApplication::Ref g_MainApplication; +ul::util::JSON g_DefaultLanguage; +ul::util::JSON g_MainLanguage; namespace ul::man::ui { void MainApplication::OnLoad() { + UL_RC_ASSERT(setInitialize()); + cfg::LoadLanguageJsons(ManagerLanguagesPath, g_MainLanguage, g_DefaultLanguage); + setExit(); + this->main_menu_lyt = MainMenuLayout::New(); this->toast = pu::ui::extras::Toast::New("...", pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::Medium), pu::ui::Color(225, 225, 225, 255), pu::ui::Color(40, 40, 40, 255)); diff --git a/projects/uManager/source/ul/man/ui/ui_MainMenuLayout.cpp b/projects/uManager/source/ul/man/ui/ui_MainMenuLayout.cpp index 0d2a73bc..e2ca3a2c 100644 --- a/projects/uManager/source/ul/man/ui/ui_MainMenuLayout.cpp +++ b/projects/uManager/source/ul/man/ui/ui_MainMenuLayout.cpp @@ -1,44 +1,75 @@ #include
                    +#include
                      #include
                        +#include
                          +#include
                            +#include extern ul::man::ui::MainApplication::Ref g_MainApplication; +extern ul::util::JSON g_DefaultLanguage; +extern ul::util::JSON g_MainLanguage; namespace ul::man::ui { namespace { + inline std::string GetLanguageString(const std::string &name) { + return cfg::GetLanguageString(g_MainLanguage, g_DefaultLanguage, name); + } + inline std::string GetStatus() { - std::string status = "Status: "; + std::string status = GetLanguageString("status") + ": "; if(IsBasePresent()) { if(IsSystemActive()) { - status += "active"; + status += GetLanguageString("status_active"); } else { - status += "not active"; + status += GetLanguageString("status_not_active"); } } else { - status += "not present"; + status += GetLanguageString("status_not_present"); } return status; } + inline void RebootSystem() { + UL_RC_ASSERT(spsmInitialize()); + spsmShutdown(true); + } + + inline void ShowUpdateError() { + g_MainApplication->CreateShowDialog(GetLanguageString("update_title"), GetLanguageString("update_error"), { GetLanguageString("ok") }, true); + } + + constexpr size_t TemporaryFileExtractBufferSize = 0x10000; + u8 g_TemporaryFileExtractBuffer[TemporaryFileExtractBufferSize]; + } MainMenuLayout::MainMenuLayout() : pu::ui::Layout() { - this->info_text = pu::ui::elm::TextBlock::New(0, 25, "uManager - uLaunch's manager, v" UL_VERSION); + this->info_text = pu::ui::elm::TextBlock::New(0, 25, "..."); + this->ResetInfoText(); this->info_text->SetFont(pu::ui::GetDefaultFont(pu::ui::DefaultFontSize::MediumLarge)); this->info_text->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center); this->info_text->SetColor(pu::ui::Color(0, 0, 0, 255)); this->Add(this->info_text); this->options_menu = pu::ui::elm::Menu::New(0, 70, pu::ui::render::ScreenWidth, pu::ui::Color(80, 0, 120, 255), pu::ui::Color(127, 0, 190, 255), (pu::ui::render::ScreenHeight - 100) / MenuItemCount, MenuItemCount); + this->activate_menu_item = pu::ui::elm::MenuItem::New(GetStatus()); this->activate_menu_item->SetColor(pu::ui::Color(225, 225, 225, 255)); this->activate_menu_item->AddOnKey(std::bind(&MainMenuLayout::activate_DefaultKey, this)); this->options_menu->AddItem(this->activate_menu_item); + + this->update_menu_item = pu::ui::elm::MenuItem::New(GetLanguageString("update_item")); + this->update_menu_item->SetColor(pu::ui::Color(225, 225, 225, 255)); + this->update_menu_item->AddOnKey(std::bind(&MainMenuLayout::update_DefaultKey, this)); + this->options_menu->AddItem(this->update_menu_item); + this->Add(this->options_menu); + this->SetBackgroundColor(pu::ui::Color(192, 128, 217, 255)); } @@ -54,16 +85,105 @@ namespace ul::man::ui { this->activate_menu_item->SetName(GetStatus()); this->options_menu->ClearItems(); this->options_menu->AddItem(this->activate_menu_item); + this->options_menu->AddItem(this->update_menu_item); - const auto option = g_MainApplication->CreateShowDialog("Changes", "The (de)activation of uLaunch needs a reboot for changes to take effect", { "Reboot", "Continue" }, true); + const auto option = g_MainApplication->CreateShowDialog(GetLanguageString("activate_changes_title"), GetLanguageString("activate_changes"), { GetLanguageString("reboot"), GetLanguageString("activate_continue") }, true); if(option == 0) { - if(R_SUCCEEDED(spsmInitialize())) { - spsmShutdown(true); - } + RebootSystem(); } } else { - g_MainApplication->CreateShowDialog("uLaunch", "uLaunch was not found in the SD card", { "Ok" }, true); + g_MainApplication->CreateShowDialog(GetLanguageString("activate_changes_title"), GetLanguageString("activate_not_present"), { GetLanguageString("ok") }, true); + } + } + + void MainMenuLayout::update_DefaultKey() { + const auto json_data = man::RetrieveContent("https://api.github.com/repos/XorTroll/uLaunch/releases", "application/json"); + if(json_data.empty()) { + ShowUpdateError(); + return; + } + + const auto json = ul::util::JSON::parse(json_data); + if(json.size() <= 0) { + ShowUpdateError(); + return; + } + + const auto last_id = json[0].value("tag_name", ""); + if(last_id.empty()) { + ShowUpdateError(); + return; + } + + const auto last_ver = Version::FromString(last_id); + const auto cur_ver = Version::FromString("0.3.1"); + if(last_ver.IsEqual(cur_ver)) { + g_MainApplication->CreateShowDialog(GetLanguageString("update_title"), GetLanguageString("update_equal"), { GetLanguageString("ok") }, true); + } + else if(last_ver.IsLower(cur_ver)) { + const auto option = g_MainApplication->CreateShowDialog(GetLanguageString("update_title"), GetLanguageString("update_version") + ": v" + last_ver.AsString() + "\n" + GetLanguageString("update_conf"), { GetLanguageString("yes"), GetLanguageString("cancel") }, true); + if(option == 0) { + this->options_menu->SetVisible(false); + this->info_text->SetText(GetLanguageString("update_progress_download")); + const auto download_url = "https://github.com/XorTroll/uLaunch/releases/download/" + last_id + "/uLaunch.zip"; + man::RetrieveToFile(download_url, TemporaryReleaseZipPath, [&](const double done, const double total) { + g_MainApplication->CallForRender(); + }); + + this->info_text->SetText(GetLanguageString("update_progress_install")); + auto zip_d = zzip_opendir(TemporaryReleaseZipPath); + if(zip_d) { + while(true) { + auto zip_entry = zzip_readdir(zip_d); + if(zip_entry == nullptr) { + break; + } + + std::string entry_name = zip_entry->d_name; + entry_name = entry_name.substr(__builtin_strlen("SdOut/")); + const auto is_dir = zip_entry->st_size == 0; + if(!entry_name.empty()) { + entry_name = "sdmc:/" + entry_name; + if(is_dir) { + mkdir(entry_name.c_str(), 777); + } + else { + remove(entry_name.c_str()); + + auto zip_f = zzip_file_open(zip_d, zip_entry->d_name, 0); + if(zip_f) { + auto f = fopen(entry_name.c_str(), "wb"); + if(f) { + auto rem_size = zip_entry->st_size; + while(rem_size > 0) { + const auto read_size = zzip_read(zip_f, g_TemporaryFileExtractBuffer, TemporaryFileExtractBufferSize); + fwrite(g_TemporaryFileExtractBuffer, read_size, 1, f); + rem_size -= read_size; + } + fclose(f); + } + zzip_close(zip_f); + } + } + } + } + zzip_closedir(zip_d); + + g_MainApplication->CreateShowDialog(GetLanguageString("update_title"), GetLanguageString("update_success"), { GetLanguageString("reboot") }, true); + RebootSystem(); + } + else { + ShowUpdateError(); + } + + this->ResetInfoText(); + this->options_menu->SetVisible(true); + remove(TemporaryReleaseZipPath); + } + } + else if(last_ver.IsHigher(cur_ver)) { + g_MainApplication->CreateShowDialog(GetLanguageString("update_title"), GetLanguageString("update_higher"), { GetLanguageString("ok") }, true); } } diff --git a/projects/uMenu/romfs/en.json b/projects/uMenu/romfs/lang/en-US.json similarity index 100% rename from projects/uMenu/romfs/en.json rename to projects/uMenu/romfs/lang/en-US.json diff --git a/projects/uMenu/romfs/ja.json b/projects/uMenu/romfs/lang/ja.json similarity index 100% rename from projects/uMenu/romfs/ja.json rename to projects/uMenu/romfs/lang/ja.json diff --git a/projects/uMenu/romfs/ko.json b/projects/uMenu/romfs/lang/ko.json similarity index 100% rename from projects/uMenu/romfs/ko.json rename to projects/uMenu/romfs/lang/ko.json diff --git a/projects/uMenu/romfs/lang/zh-Hans.json b/projects/uMenu/romfs/lang/zh-Hans.json new file mode 100644 index 00000000..f2cd1c50 --- /dev/null +++ b/projects/uMenu/romfs/lang/zh-Hans.json @@ -0,0 +1,179 @@ +{ + "yes": "是", + "no": "否", + "ok": "好的", + "cancel": "取消", + "menu_select": "选择", + "menu_selection": "选择的项目", + "menu_select_cancel_conf": "是否取消当前选择?", + "menu_select_cancel": "选择已取消", + "menu_rename_folder": "重命名文件夹", + "menu_rename_folder_conf": "是否重命名此文件夹?", + "swkbd_rename_folder_guide": "输入新建文件夹名称", + "hb_mode_entries_add": "是否将所有选项添加到主菜单?", + "hb_mode_entries_added": "所选项已添加到主菜单中", + "hb_mode_entries_some_added": "一些选定的项目已添加到主菜单中,但其他项目已添加", + "menu_move_to_folder_conf": "是否要移动此文件夹中的所有选定项目处?", + "menu_move_ok": "选项移动成功", + "menu_move_new_folder": "新建文件夹", + "menu_move_around_entry_conf": "是否将所有选定的项目点移动到此项目处?", + "menu_move_around_entry_before": "移动到之前项目", + "menu_move_around_entry_after": "在>(大于)项目后移动", + "menu_move_around_entry_swap": "与项目交换", + "menu_move_existing_folder": "现有文件夹", + "menu_move_select_folder": "选择要将所选内容移动到的文件夹.", + "menu_move_select_folder_cancel": "文件夹选择已取消.", + "menu_move_existing_folder_conf": "是否将所有选定的项目移动到此文件夹中?", + "swkbd_new_folder_guide": "输入文件夹名称", + "menu_move_from_folder": "是否将所有选定项目移回主菜单?", + "menu_move_folder_itself": "无法将文件夹移动到自身中!", + "menu_new_entry_options": "新建菜单项选项", + "menu_new_entry": "你想在菜单上添加什么?", + "menu_new_folder": "创建新建文件夹", + "menu_add_hb": "添加Homebrew所选项", + "menu_folder_created": "已成功创建新建文件夹.", + "app_launch_error": "尝试启动标题时出错", + "entry_options": "项目设置", + "entry_action": "您希望如何处理所选项?", + "entry_move": "移动到文件夹/从文件夹移动", + "entry_remove": "移除此项", + "entry_remove_conf": "是否确实要从主菜单中删除此项的访问权限?", + "entry_remove_special": "这是一个特殊的自制项目,不能删除.", + "entry_remove_ok": "已成功删除该项.", + "hbmenu_launch": "启动Hbmenu", + "unknown": "未能识别", + "power_dialog": "电源对话框", + "power_dialog_info": "你想做什么?", + "power_sleep": "休眠", + "power_power_off": "关闭电源", + "power_reboot": "重启电源", + "folder_entry_single": "打开文件夹单选项", + "folder_entry_mult": "打开文件夹多选项", + "app_launch": "启动程序标题", + "app_not_launchable": "此应用程序无法启动(游戏卡未插入、存档、未下载等)", + "app_no_take_over_title": "没有指定由自制软件接管的标题.", + "app_take_over_title_select": "按[方向上]键选择一个", + "app_take_over_select": "你想选择此标题来启动Homeberw吗?", + "app_take_over_selected": "如果选中此处Homeberw将作为该标题的应用程序启动.", + "app_take_over_done": "此标题被选择用于Homeberw的发布.", + "app_unexpected_error": "标题无法启动、崩溃或意外终止。\n(可能已损坏?如果是游戏卡标题,是否已插入?)", + "ulaunch_about": "关于 uLaunch", + "ulaunch_desc": "uLaunch是一个面向扩展性的Homeberw程序的、FOSS主页菜单替代品。请注意,一些原始的主页菜单功能尚未实现。\n\n如果您正在寻找新的uLaunchThemes,请检查r/uLaunch Themes的子版块。如果你想投稿,请查看uLaunch的GitHub存储库", + "control_minus": "把主页菜单交换为...", + "suspended_app": "此标题暂停", + "suspended_close": "是否关闭此标题[结束此程序]?所有未保存的数据都将丢失.", + "hb_launch": "启动Homebrew", + "hb_launch_conf": "你想怎样启动Homeberw?", + "hb_applet": "自制插件[小程序]", + "hb_app": "应用程序", + "user_settings": "用户设置", + "user_selected": "选择用户", + "user_option": "你想对此用户做什么?", + "user_pass_reg": "设置密码", + "user_pass_ch": "管理并应用密码", + "user_view_page": "查看用户页面", + "user_logoff": "注销此用户", + "user_pass_ch_option": "你希望如何处理此密码?", + "user_pass_change": "更改用户密码", + "user_pass_remove": "移除此用户", + "swkbd_user_pass_guide": "请输入密码", + "swkbd_user_new_pass_guide": "请输入需要更改的新密码", + "user_pass_change_conf": "是否更改现在的密码?", + "user_pass_change_ok": "密码已成功更改.", + "user_pass_change_error": "试图更改密码时出现错误", + "user_pass_remove_full": "删除密码", + "user_pass_remove_conf": "是否删除此用户密码?", + "user_pass_remove_ok": "成功删除此密码.", + "user_pass_remove_error": "试图更改密码时出错", + "user_pass_reg_conf": "是否为当前用户设置密码?", + "user_pass_reg_ok": "密码设置成功", + "user_pass_reg_error": "试图更改密码时出错", + "user_logoff_app_suspended": "你打开此标题项后,是否真的想要关闭此标题项?", + "swkbd_webpage_guide": "输入网页地址", + "set_unknown_value": "未能识别", + "set_true_value": "对/是", + "set_false_value": "错/否", + "set_info_text": "浏览和/或编辑系统和uLaunch设置", + "set_console_nickname": "控制台昵称", + "set_console_timezone": "控制台时区位置", + "set_viewer_enabled": "屏幕阅览器的USB连接是否启用?", + "set_flog_enabled": "Homebrew 应用程序模式的“flog”接管项 已启用", + "set_wifi_none": "无 (没有无线网络连接)", + "set_wifi_name": "已连接至无线互联网", + "set_console_lang": "控制台语言", + "set_console_info_upload": "控制台信息[INFO] 已启用", + "set_auto_titles_dl": "自动下载应用程序 已启用", + "set_auto_update": "控制台自动升级 已启用", + "set_wireless_lan": "有线LAN互联网连接 已启用", + "set_bluetooth": "蓝牙 已启用", + "set_usb_30": "USB 3.0 已启用", + "set_nfc": "NFC 已启用 (amiibo)", + "set_serial_no": "控制台序列号", + "set_mac_addr": "MAC 地址", + "set_ip_addr": "IP 地址", + "swkbd_console_nick_guide": "输入新的控制台昵称", + "set_enable_conf": "是否想要启用此项?", + "set_disable_conf": "是否想要关闭此项?", + "set_changed_reboot": "完成.需要重新启动(在此之前不会使用该选项)", + "set_viewer_info": "如果要使用电脑屏幕阅览器(uViewer),则必须启用此功能。否则无需启用此功能.", + "set_flog_info": "必须启用此功能,才能将Homeberw程序直接作为应用程序启动。请注意,这可能涉及BAN RISK.", + "startup_welcome_info": "欢迎!请选择要使用的帐户.", + "startup_login_error": "密码无效/错误,请尝试后重新输入.", + "startup_password": "(密码)", + "startup_new_user": "创建新用户", + "theme_current": "当前主题", + "theme_no_custom": "你似乎还没有自定义主题.", + "theme_reset": "重置为默认主题", + "theme_by": "此主题由", + "theme_reset_conf": "是否重置并使用uLaunch的默认主题?", + "theme_changed": "uLaunch's theme 已重置.", + "theme_active_this": "此为当前动态主题.", + "theme_set": "设置主题", + "theme_set_conf": "是否将此主题设置为动态主题?", + "theme_outdated": "此主题已不适配当前版本uLaunch程序", + "lang_info_text": "请选择当前控制台的语言." , + "lang_selected": "(选择)", + "lang_set": "选择控制台语言", + "lang_active_this": "此语言为当前控制台语言.", + "lang_set_conf": "是否要将此语言设置为控制台的语言?完成此操作后,控制台将重新启动.", + "lang_set_ok": "控制台的语言已更改。关闭此对话框以重新启动.", + "lang_set_error": "试图更改控制台的语言时出错", + "help_title": "uLaunch 帮助", + "help_launch": "按'A'键启动所选项,或在暂停时返回到选项.", + "help_close": "按'X'键关闭当前打开并选择的标题项.", + "help_quick": "按'L'键/'右'摇杆或'L'键 / 'R'键 / 'ZL'键 / 'ZR'键以打开快捷菜单栏.然后.按'A'键 以此选项为中心,或按'B'键 取消.", + "help_select": "按'Y'键打开选择模式。然后,按'Y'键(取消)选择任何标题,按'Y'键 确认选择,或按'B'键 取消.", + "help_back": "在任何菜单(启动菜单除外)上按'B'键 或 'HOME'键 可返回'主页'.", + "help_minus": "按主菜单上的减号(-)键,在普通菜单和Homeberw菜单之间切换.", + "help_plus": "按加号(+)键查看uLaunch的信息(项目版本、说明…)", + "controller_support_explain_text": "文本说明", + "input_move_selection": "移动所选项(剪切/粘贴)", + "input_resume_suspended": "重命名", + "input_open_folder": "打开", + "input_launch_entry": "启动", + "input_cancel_selection": "取消所选项", + "input_close_suspended": "关闭", + "input_entry_options": "设置", + "input_select_entry": "选择", + "input_folder_back": "返回", + "input_quick_menu": "快速启动", + "input_resize_menu": "调整大小", + "input_new_entry": "新建项目", + "menu_chosen_hb": "选择Homebrew程序", + "menu_chosen_hb_info": "选择以下Homeberw程序:", + "menu_add_chosen_hb": "你想把这个Homeberw程序添加进菜单中吗?", + "menu_chosen_hb_added": "此Homeberw程序已被成功添加!.", + "gamecard": "游戏卡带", + "gamecard_mount_failed": "游戏卡带加载失败:", + "sd_card": "TF/SD内存卡", + "sd_card_ejected": "TF/SD内存卡已安全弹出,您必须关闭或重启控制台.", + "shutdown": "关闭", + "reboot": "重启", + "quick_power_options": "电源设置", + "quick_controller_options": "手柄/控制器设置", + "quick_album": "启用 Album/Hbmenu", + "quick_web_page": "打开网页", + "quick_user_menu": "用户菜单", + "quick_themes_menu": "主题菜单", + "quick_settings_menu": "设置菜单" +} \ No newline at end of file diff --git a/projects/uMenu/source/main.cpp b/projects/uMenu/source/main.cpp index e5fea3fe..46114f40 100644 --- a/projects/uMenu/source/main.cpp +++ b/projects/uMenu/source/main.cpp @@ -89,16 +89,7 @@ int main() { Initialize(); // Get system language and load translations (default one if not present) - u64 lang_code = 0; - UL_RC_ASSERT(setGetLanguageCode(&lang_code)); - const auto lang_path = ul::cfg::GetLanguageJSONPath(reinterpret_cast(&lang_code)); - UL_RC_ASSERT(ul::util::LoadJSONFromFile(g_DefaultLanguage, ul::DefaultLanguagePath)); - g_MainLanguage = g_DefaultLanguage; - if(ul::fs::ExistsFile(lang_path)) { - auto lang_json = ul::util::JSON::object(); - UL_RC_ASSERT(ul::util::LoadJSONFromFile(lang_json, lang_path)); - g_MainLanguage = lang_json; - } + ul::cfg::LoadLanguageJsons(ul::MenuLanguagesPath, g_MainLanguage, g_DefaultLanguage); // Get the text sizes to initialize default fonts auto ui_json = ul::util::JSON::object();