Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e032ca1f |
@@ -1,23 +0,0 @@
|
||||
[meta]
|
||||
name="White not finished"
|
||||
author=TotalJustice
|
||||
version=1.0.0
|
||||
preview=romfs:/theme/preview.jpg
|
||||
|
||||
[theme]
|
||||
background=0xEBEBEBff
|
||||
cursor=romfs:/theme/cursor.png
|
||||
cursor_drag=romfs:/theme/cursor_drag.png
|
||||
grid=0x46464630
|
||||
selected=0x464646ff
|
||||
selected_overlay=0x00ffc8ff
|
||||
text=0x2D2D2Dff
|
||||
text_selected=0x3A50F0ff
|
||||
|
||||
icon_audio=romfs:/theme/icon_audio.png
|
||||
icon_video=romfs:/theme/icon_video.png
|
||||
icon_image=romfs:/theme/icon_image.png
|
||||
icon_file=romfs:/theme/icon_file.png
|
||||
icon_folder=romfs:/theme/icon_folder.png
|
||||
icon_zip=romfs:/theme/icon_zip.png
|
||||
icon_nro=romfs:/theme/icon_nro.png
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(sphaira_VERSION 0.3.0)
|
||||
set(sphaira_VERSION 0.4.0)
|
||||
|
||||
project(sphaira
|
||||
VERSION ${sphaira_VERSION}
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace sphaira::i18n {
|
||||
bool init(long index);
|
||||
void exit();
|
||||
|
||||
std::string get(const char* str);
|
||||
|
||||
} // namespace sphaira::i18n
|
||||
|
||||
inline namespace literals {
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
|
||||
namespace sphaira::ui::menu::main {
|
||||
|
||||
enum class UpdateState {
|
||||
// still downloading json from github
|
||||
Pending,
|
||||
// no update available.
|
||||
None,
|
||||
// update available!
|
||||
Update,
|
||||
};
|
||||
|
||||
// this holds 2 menus and allows for switching between them
|
||||
struct MainMenu final : Widget {
|
||||
MainMenu();
|
||||
@@ -31,7 +40,7 @@ private:
|
||||
std::string m_update_url{};
|
||||
std::string m_update_version{};
|
||||
std::string m_update_description{};
|
||||
bool m_update_avaliable{};
|
||||
UpdateState m_update_state{UpdateState::Pending};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::main
|
||||
|
||||
@@ -1046,6 +1046,29 @@ App::~App() {
|
||||
} else {
|
||||
log_write("success with copying over root file!\n");
|
||||
}
|
||||
} else if (IsHbmenu()) {
|
||||
// check we have a version that's newer than current.
|
||||
fs::FsNativeSd fs;
|
||||
NacpStruct sphaira_nacp;
|
||||
fs::FsPath sphaira_path = "/switch/sphaira/sphaira.nro";
|
||||
Result rc;
|
||||
|
||||
rc = nro_get_nacp(sphaira_path, sphaira_nacp);
|
||||
if (R_FAILED(rc) || std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
|
||||
sphaira_path = "/switch/sphaira.nro";
|
||||
rc = nro_get_nacp(sphaira_path, sphaira_nacp);
|
||||
}
|
||||
|
||||
// found sphaira, now lets get compare version
|
||||
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
|
||||
if (std::strcmp(APP_VERSION, sphaira_nacp.display_version) < 0) {
|
||||
if (R_FAILED(rc = fs.copy_entire_file(GetExePath(), sphaira_path, true))) {
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
} else {
|
||||
log_write("success with updating hbmenu!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (App::GetNxlinkEnable()) {
|
||||
|
||||
@@ -11,7 +11,7 @@ std::vector<u8> g_i18n_data;
|
||||
yyjson_doc* json;
|
||||
yyjson_val* root;
|
||||
|
||||
std::string get(const char* str, size_t len) {
|
||||
std::string get_internal(const char* str, size_t len) {
|
||||
if (!json || !root) {
|
||||
log_write("no json or root\n");
|
||||
return str;
|
||||
@@ -117,12 +117,16 @@ void exit() {
|
||||
g_i18n_data.clear();
|
||||
}
|
||||
|
||||
std::string get(const char* str) {
|
||||
return get_internal(str, std::strlen(str));
|
||||
}
|
||||
|
||||
} // namespace sphaira::i18n
|
||||
|
||||
namespace literals {
|
||||
|
||||
std::string operator"" _i18n(const char* str, size_t len) {
|
||||
return sphaira::i18n::get(str, len);
|
||||
return sphaira::i18n::get_internal(str, len);
|
||||
}
|
||||
|
||||
} // namespace literals
|
||||
|
||||
@@ -1438,7 +1438,7 @@ void Menu::Sort() {
|
||||
|
||||
|
||||
char subheader[128]{};
|
||||
std::snprintf(subheader, sizeof(subheader), "Sort: %s | Filter: %s | Order: %s", SORT_STR[m_sort], FILTER_STR[m_filter], ORDER_STR[m_order]);
|
||||
std::snprintf(subheader, sizeof(subheader), "Sort: %s | Filter: %s | Order: %s", i18n::get(SORT_STR[m_sort]), i18n::get(FILTER_STR[m_filter]), i18n::get(ORDER_STR[m_order]));
|
||||
SetTitleSubHeading(subheader);
|
||||
|
||||
std::sort(m_entries_current.begin(), m_entries_current.end(), sorter);
|
||||
|
||||
@@ -17,38 +17,6 @@
|
||||
namespace sphaira::ui::menu::homebrew {
|
||||
namespace {
|
||||
|
||||
constexpr const char* SORT_STR[] = {
|
||||
"Updated",
|
||||
"Size",
|
||||
"Alphabetical",
|
||||
};
|
||||
|
||||
constexpr const char* ORDER_STR[] = {
|
||||
"Desc",
|
||||
"Asc",
|
||||
};
|
||||
|
||||
// returns seconds as: hh:mm:ss
|
||||
auto TimeFormat(u64 sec) -> std::string {
|
||||
char buf[9];
|
||||
|
||||
const auto s = sec % 60;
|
||||
const auto h = sec / 60 % 60;
|
||||
const auto d = sec / 60 / 60 % 24;
|
||||
|
||||
if (sec < 60) {
|
||||
if (!sec) {
|
||||
return "00:00:00";
|
||||
}
|
||||
std::snprintf(buf, sizeof(buf), "00:00:%02lu", s);
|
||||
} else if (sec < 3600) {
|
||||
std::snprintf(buf, sizeof(buf), "00:%02lu:%02lu", h, s);
|
||||
} else {
|
||||
std::snprintf(buf, sizeof(buf), "%02lu:%02lu:%02lu", d, h, s);
|
||||
}
|
||||
|
||||
return std::string{buf};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
#include "ui/menus/irs_menu.hpp"
|
||||
#include "ui/menus/themezer.hpp"
|
||||
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "ui/error_box.hpp"
|
||||
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
#include "download.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "ui/menus/irs_menu.hpp"
|
||||
#include "ui/menus/themezer.hpp"
|
||||
#include "web.hpp"
|
||||
#include "i18n.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <minizip/unzip.h>
|
||||
|
||||
namespace sphaira::ui::menu::main {
|
||||
namespace {
|
||||
|
||||
#if 0
|
||||
bool parseSearch(const char *parse_string, const char *filter, char* new_string) {
|
||||
char c;
|
||||
u32 offset = 0;
|
||||
@@ -40,37 +44,159 @@ bool parseSearch(const char *parse_string, const char *filter, char* new_string)
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string version) -> bool {
|
||||
static fs::FsPath zip_out{"/switch/sphaira/cache/update.zip"};
|
||||
constexpr auto chunk_size = 1024 * 512; // 512KiB
|
||||
|
||||
fs::FsNativeSd fs;
|
||||
R_TRY_RESULT(fs.GetFsOpenResult(), false);
|
||||
|
||||
// 1. download the zip
|
||||
if (!pbox->ShouldExit()) {
|
||||
pbox->NewTransfer("Downloading "_i18n + version);
|
||||
log_write("starting download: %s\n", url.c_str());
|
||||
|
||||
DownloadClearCache(url);
|
||||
if (!DownloadFile(url, zip_out, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){
|
||||
if (pbox->ShouldExit()) {
|
||||
return false;
|
||||
}
|
||||
pbox->UpdateTransfer(dlnow, dltotal);
|
||||
return true;
|
||||
})) {
|
||||
log_write("error with download\n");
|
||||
// push popup error box
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ON_SCOPE_EXIT(fs.DeleteFile(zip_out));
|
||||
|
||||
// 2. extract the zip
|
||||
if (!pbox->ShouldExit()) {
|
||||
auto zfile = unzOpen64(zip_out);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", zip_out);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
|
||||
unz_global_info64 pglobal_info;
|
||||
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pglobal_info.number_entry; i++) {
|
||||
if (i > 0) {
|
||||
if (UNZ_OK != unzGoToNextFile(zfile)) {
|
||||
log_write("failed to unzGoToNextFile\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
|
||||
log_write("failed to open current file\n");
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
|
||||
|
||||
unz_file_info64 info;
|
||||
fs::FsPath file_path;
|
||||
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, file_path, sizeof(file_path), 0, 0, 0, 0)) {
|
||||
log_write("failed to get current info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_path[0] != '/') {
|
||||
file_path = fs::AppendPath("/", file_path);
|
||||
}
|
||||
|
||||
Result rc;
|
||||
if (file_path[strlen(file_path) -1] == '/') {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_ResultPathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<char> buf(chunk_size);
|
||||
u64 offset{};
|
||||
while (offset < info.uncompressed_size) {
|
||||
if (pbox->ShouldExit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
|
||||
if (bytes_read <= 0) {
|
||||
// log_write("failed to read zip file: %s\n", inzip.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
pbox->UpdateTransfer(offset, info.uncompressed_size);
|
||||
offset += bytes_read;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_write("finished update :)\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MainMenu::MainMenu() {
|
||||
#if 0
|
||||
DownloadMemoryAsync("https://api.github.com/repos/ITotalJustice/sys-patch/releases/latest", [this](std::vector<u8>& data, bool success){
|
||||
// todo: replace below with yyjson, this is old code from ams updater, lol.
|
||||
DownloadMemoryAsync("https://api.github.com/repos/ITotalJustice/sphaira/releases/latest", "", [this](std::vector<u8>& data, bool success){
|
||||
data.push_back('\0');
|
||||
auto raw_str = (const char*)data.data();
|
||||
char out_str[0x301];
|
||||
|
||||
if (parseSearch(raw_str, "tag_name\":\"", out_str)) {
|
||||
m_update_version = out_str;
|
||||
if (strcasecmp("v1.5.0", m_update_version.c_str())) {
|
||||
m_update_avaliable = true;
|
||||
if (std::strcmp(APP_VERSION, m_update_version.c_str()) < 0) {
|
||||
m_update_state = UpdateState::Update;
|
||||
App::Notify("Update avaliable: "_i18n + m_update_version);
|
||||
} else {
|
||||
m_update_state = UpdateState::None;
|
||||
}
|
||||
log_write("FOUND IT : %s\n", out_str);
|
||||
log_write("found update tag : %s vs %s\n", APP_VERSION, out_str);
|
||||
}
|
||||
|
||||
if (parseSearch(raw_str, "browser_download_url\":\"", out_str)) {
|
||||
m_update_url = out_str;
|
||||
log_write("FOUND IT : %s\n", out_str);
|
||||
log_write("found download url : %s\n", out_str);
|
||||
}
|
||||
|
||||
if (parseSearch(raw_str, "body\":\"", out_str)) {
|
||||
m_update_description = out_str;
|
||||
// m_update_description.replace("\r\n\r\n", "\n");
|
||||
log_write("FOUND IT : %s\n", out_str);
|
||||
log_write("found description : %s\n", out_str);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
AddOnLPress();
|
||||
AddOnRPress();
|
||||
@@ -128,22 +254,26 @@ MainMenu::MainMenu() {
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [this](bool& enable){
|
||||
App::SetNxlinkEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Check for update"_i18n, [this](){
|
||||
App::Notify("Not Implemented"_i18n);
|
||||
}));
|
||||
|
||||
if (m_update_state == UpdateState::Update) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
|
||||
App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + m_update_version, [this](auto pbox){
|
||||
return InstallUpdate(pbox, m_update_url, m_update_version);
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
m_update_state = UpdateState::None;
|
||||
} else {
|
||||
App::Push(std::make_shared<ui::ErrorBox>(MAKERESULT(351, 1), "Failed to download update"));
|
||||
}
|
||||
}, 2));
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this, language_items](std::size_t& index_out){
|
||||
App::SetLanguage(index_out);
|
||||
}, (std::size_t)App::GetLanguage()));
|
||||
|
||||
if (m_update_avaliable) {
|
||||
std::string str = "Update avaliable: "_i18n + m_update_version;
|
||||
options->Add(std::make_shared<SidebarEntryCallback>(str, [this](){
|
||||
App::Notify("Not Implemented"_i18n);
|
||||
}));
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [this](bool& enable){
|
||||
App::SetLogEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
Reference in New Issue
Block a user