diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp index 58d48f30..6346f436 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp @@ -20,8 +20,8 @@ #pragma once -#define CUST_REV 6 -#define KIP_VERSION 251 +#define CUST_REV 7 +#define KIP_VERSION 260 #include "oc_common.hpp" #include "pcv/pcv_common.hpp" diff --git a/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp index 3a339d5b..6a2b2383 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp @@ -84,3 +84,22 @@ void InfoGui::listUI() { this->listElement->addItem(gap, PARA_GAP); } } + +void addWrappedTextItems(tsl::elm::List *list, const std::vector ¶graphs) { + const s32 maxWidth = tsl::cfg::FramebufferWidth - MARGIN_L - MARGIN_R; + + for (const auto ¶ : paragraphs) { + for (const auto &lineText : wrapText(para, maxWidth)) { + auto *d = new FocusableDrawer([lineText](tsl::gfx::Renderer *r, s32 x, s32 y, s32 w, s32 h) { + r->drawString((lineText + "\n").c_str(), false, x + MARGIN_L, y + LINE_H - 5, TEXT_SIZE, tsl::style::color::ColorText); + }); + d->setBoundaries(0, 0, tsl::cfg::FramebufferWidth, LINE_H); + list->addItem(d, LINE_H); + } + + // paragraph gap + auto *gap = new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *, s32, s32, s32, s32) {}); + gap->setBoundaries(0, 0, tsl::cfg::FramebufferWidth, PARA_GAP); + list->addItem(gap, PARA_GAP); + } +} \ No newline at end of file diff --git a/Source/hoc-clk/overlay/src/ui/gui/info_gui.h b/Source/hoc-clk/overlay/src/ui/gui/info_gui.h index 0721060d..4850eae6 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/info_gui.h +++ b/Source/hoc-clk/overlay/src/ui/gui/info_gui.h @@ -21,6 +21,8 @@ #include "base_menu_gui.h" +void addWrappedTextItems(tsl::elm::List *list, const std::vector ¶graphs); + class InfoGui : public BaseMenuGui { public: InfoGui(std::string title, std::vector strings); diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.cpp new file mode 100644 index 00000000..8341bd99 --- /dev/null +++ b/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "update_confirm_gui.h" +#include "info_gui.h" +#include "update_gui.h" + +#include + +UpdateConfirmGui::UpdateConfirmGui(UpdateGui *parent, int packageIndex, std::string packageName, + std::string tag, std::vector changelog) + : m_parent(parent), m_packageIndex(packageIndex), m_packageName(std::move(packageName)), + m_tag(std::move(tag)), m_changelog(std::move(changelog)) { +} + +void UpdateConfirmGui::listUI() { + std::string header = m_packageName; + if (!m_tag.empty()) + header += " " + m_tag; + this->listElement->addItem(new tsl::elm::CategoryHeader(header)); + + addWrappedTextItems(this->listElement, m_changelog); + + this->listElement->addItem(new tsl::elm::CategoryHeader("Confirm")); + + auto *installItem = new tsl::elm::ListItem("Install Update"); + installItem->setValue(""); + installItem->setClickListener([this](u64 keys) -> bool { + if ((keys & HidNpadButton_A) == HidNpadButton_A) { + m_parent->startJob(m_packageIndex, false); + tsl::goBack(); + return true; + } + return false; + }); + this->listElement->addItem(installItem); +} + +bool UpdateConfirmGui::handleInput(u64, u64, const HidTouchState &, + HidAnalogStickState, HidAnalogStickState) { + return false; +} diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.h b/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.h new file mode 100644 index 00000000..3439f0fc --- /dev/null +++ b/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "base_menu_gui.h" + +class UpdateGui; + +class UpdateConfirmGui : public BaseMenuGui { +public: + UpdateConfirmGui(UpdateGui *parent, int packageIndex, std::string packageName, + std::string tag, std::vector changelog); + ~UpdateConfirmGui() = default; + + void listUI() override; + bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, + HidAnalogStickState leftJoy, HidAnalogStickState rightJoy) override; + +private: + UpdateGui *m_parent; + int m_packageIndex; + std::string m_packageName; + std::string m_tag; + std::vector m_changelog; +}; diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp index 7160c946..c56c8d5c 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp @@ -17,12 +17,14 @@ #include "update_gui.h" #include "ult_ext.h" #include "base_menu_gui.h" +#include "update_confirm_gui.h" #include "../format.h" #include "../style.h" #include #include #include +#include #include @@ -131,6 +133,29 @@ void UpdateGui::startJob(int packageIndex, bool extractOnly) { } } +void UpdateGui::startChangelogFetch(int packageIndex) { + if (isBusy()) return; + + m_activePackage = packageIndex; + m_resultMessage.clear(); + m_changelogTag.clear(); + m_changelogLines.clear(); + + ult::abortDownload.store(false, std::memory_order_release); + + m_stage.store(UpdateStage::FetchingChangelog, std::memory_order_release); + + if (R_SUCCEEDED(threadCreate(&m_thread, fetchChangelogEntry, this, + nullptr, 0x8000, 0x20, -2))) { + m_threadActive = true; + threadStart(&m_thread); + } else { + m_resultMessage = "Failed to create thread"; + m_stage.store(UpdateStage::Failed, std::memory_order_release); + m_threadActive = false; + } +} + void UpdateGui::requestCancel() { ult::abortDownload.store(true, std::memory_order_release); ult::abortUnzip.store(true, std::memory_order_release); @@ -186,6 +211,55 @@ void UpdateGui::jobBody() { m_stage.store(UpdateStage::Done, std::memory_order_release); } +/* static */ void UpdateGui::fetchChangelogEntry(void *arg) { + static_cast(arg)->fetchChangelogBody(); +} + +void UpdateGui::fetchChangelogBody() { + ult::createDirectory("sdmc:/config/horizon-oc/"); + + bool ok = ult::downloadFile(kReleaseApiUrl, kReleaseInfoPath, false, false); + if (!ok || ult::abortDownload.load(std::memory_order_acquire)) { + m_resultMessage = ult::abortDownload.load(std::memory_order_acquire) + ? "Changelog fetch cancelled" + : "Could not fetch changelog -- check network"; + m_stage.store( + ult::abortDownload.load() ? UpdateStage::Cancelled : UpdateStage::Failed, + std::memory_order_release); + return; + } + + std::unique_ptr root(ult::readJsonFromFile(kReleaseInfoPath)); + std::remove(kReleaseInfoPath); + + if (!root) { + m_resultMessage = "Could not parse changelog"; + m_stage.store(UpdateStage::Failed, std::memory_order_release); + return; + } + + m_changelogTag = ult::getStringFromJson(root.get(), "tag_name"); + const std::string body = ult::getStringFromJson(root.get(), "body"); + + m_changelogLines.clear(); + size_t pos = 0; + while (pos <= body.size()) { + size_t nl = body.find('\n', pos); + std::string line = (nl == std::string::npos) ? body.substr(pos) : body.substr(pos, nl - pos); + if (!line.empty() && line.back() == '\r') + line.pop_back(); + if (!line.empty()) + m_changelogLines.push_back(line); + if (nl == std::string::npos) + break; + pos = nl + 1; + } + if (m_changelogLines.empty()) + m_changelogLines.push_back("No changelog available for this release."); + + m_stage.store(UpdateStage::ChangelogReady, std::memory_order_release); +} + void UpdateGui::listUI() { BaseMenuGui::refresh(); @@ -201,7 +275,12 @@ void UpdateGui::listUI() { item->setClickListener([this, idx](u64 keys) -> bool { if (isBusy()) return false; if (keys & HidNpadButton_A) { - startJob(idx, false); + if (ult::limitedMemory) { + m_status->setState(4, 100, kPackages[idx].displayName, + "Updates cannot be performed on a 4MB overlay heap"); + return true; + } + startChangelogFetch(idx); return true; } return false; @@ -242,6 +321,24 @@ void UpdateGui::pollJob() { return; } + if (stage == UpdateStage::FetchingChangelog) { + m_status->setState(6, 0, pkgName, "Fetching changelog..."); + if (m_items[m_activePackage]) + m_items[m_activePackage]->setValue("..."); + return; + } + + if (stage == UpdateStage::ChangelogReady) { + reapThread(); + m_stage.store(UpdateStage::Idle, std::memory_order_release); + m_status->setState(0, 0, "", ""); + if (m_items[m_activePackage]) + m_items[m_activePackage]->setValue(""); + + m_pendingChangelogConfirm = true; + return; + } + int panelStage = (stage == UpdateStage::Done) ? 3 : (stage == UpdateStage::Failed) ? 4 : 5; @@ -263,6 +360,13 @@ bool UpdateGui::handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoy, HidAnalogStickState rightJoy) { + if (m_pendingChangelogConfirm) { + m_pendingChangelogConfirm = false; + tsl::changeTo(this, m_activePackage, kPackages[m_activePackage].displayName, + m_changelogTag, m_changelogLines); + return true; + } + if ((keysDown & HidNpadButton_R) && isBusy()) { requestCancel(); return true; diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_gui.h b/Source/hoc-clk/overlay/src/ui/gui/update_gui.h index f81e821b..17b5dd04 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/update_gui.h +++ b/Source/hoc-clk/overlay/src/ui/gui/update_gui.h @@ -19,6 +19,7 @@ #include #include +#include #include "base_menu_gui.h" @@ -35,6 +36,8 @@ public: bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoy, HidAnalogStickState rightJoy) override; + void startJob(int packageIndex, bool extractOnly); + private: enum class UpdateStage { Idle, @@ -43,6 +46,8 @@ private: Done, Failed, Cancelled, + FetchingChangelog, + ChangelogReady, }; struct PackageInfo { @@ -56,11 +61,14 @@ private: {"Horizon OC + Extensions", "https://github.com/Horizon-OC/Horizon-OC/releases/latest/download/dist_ext.zip", "sdmc:/config/horizon-oc/dist_ext.zip"}, }; + static constexpr const char *kReleaseApiUrl = "https://api.github.com/repos/Horizon-OC/Horizon-OC/releases/latest"; + static constexpr const char *kReleaseInfoPath = "sdmc:/config/horizon-oc/release_info.json"; + bool isBusy() const { return m_stage.load(std::memory_order_acquire) != UpdateStage::Idle; } - void startJob(int packageIndex, bool extractOnly); + void startChangelogFetch(int packageIndex); void requestCancel(); void reapThread(); void pollJob(); @@ -68,6 +76,9 @@ private: static void jobEntry(void *arg); void jobBody(); + static void fetchChangelogEntry(void *arg); + void fetchChangelogBody(); + std::atomic m_stage{UpdateStage::Idle}; int m_activePackage = -1; bool m_extractOnly = false; @@ -75,6 +86,11 @@ private: Thread m_thread{}; std::string m_resultMessage; + std::string m_changelogTag; + std::vector m_changelogLines; + + bool m_pendingChangelogConfirm = false; + tsl::elm::ListItem *m_items[2] = {nullptr, nullptr}; UpdateStatusPanel *m_status = nullptr; diff --git a/Source/hoc-clk/sysmodule/Makefile b/Source/hoc-clk/sysmodule/Makefile index 899ddabb..699c7e90 100644 --- a/Source/hoc-clk/sysmodule/Makefile +++ b/Source/hoc-clk/sysmodule/Makefile @@ -28,9 +28,9 @@ INCLUDES := ../common/include src/hos src/soc src/i2c src/util src/pwr src/ipc EXEFS_SRC := exefs_src LIBNAMES := minIni # major minor patch -TARGET_VERSION := 2.5.1 -KIP_VERSION := 251 -CUST_REV := 6 +TARGET_VERSION := 2.6.0 +KIP_VERSION := 260 +CUST_REV := 7 #--------------------------------------------------------------------------------- # options for code generation