From e84723abd512740550e07fccd5f5cc8f87b8f64f Mon Sep 17 00:00:00 2001 From: Joel16 Date: Mon, 6 Jun 2022 09:46:51 -0400 Subject: [PATCH] applist: Add option to sort apps inside folders/outside only and introduce new settings menu to access beta features --- CMakeLists.txt | 4 +- dbbrowser.cpp | 40 +++++++++++++++ dbbrowser.h | 11 +++++ include/applist.h | 2 - include/config.h | 18 +++++++ include/fs.h | 1 + source/applist.cpp | 21 ++++---- source/config.cpp | 117 +++++++++++++++++++++++++++++++++++++++++++ source/fs.cpp | 23 ++++++--- source/gui.cpp | 120 ++++++++++++++++++++++++++++++++++----------- source/main.cpp | 7 +-- 11 files changed, 314 insertions(+), 50 deletions(-) create mode 100644 dbbrowser.cpp create mode 100644 dbbrowser.h create mode 100644 include/config.h create mode 100644 source/config.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 11b1f0b..be3b7bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ add_definitions( -DIMGUI_DISABLE_WIN32_FUNCTIONS -DIMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -DIMGUI_ENABLE_FREETYPE ) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -ffast-math -mtune=cortex-a9 -mfpu=neon -Wall -Wno-psabi -std=gnu++17") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -ffast-math -mtune=cortex-a9 -mfpu=neon -Wall -Wno-psabi -fno-rtti -std=gnu++17") set(VITA_MKSFOEX_FLAGS "${VITA_MKSFOEX_FLAGS} -d PARENTAL_LEVEL=1") @@ -45,6 +45,7 @@ add_executable(${PROJECT_NAME} libs/imgui/imgui_widgets.cpp libs/imgui/misc/freetype/imgui_freetype.cpp source/applist.cpp + source/config.cpp source/fs.cpp source/gui.cpp source/imgui_impl_vitagl.cpp @@ -75,6 +76,7 @@ target_link_libraries(${PROJECT_NAME} SceDisplay_stub SceGxm_stub SceKernelDmacMgr_stub + SceLibJson_stub SceLibKernel_stub ScePower_stub SceShaccCg_stub diff --git a/dbbrowser.cpp b/dbbrowser.cpp new file mode 100644 index 0000000..251aa74 --- /dev/null +++ b/dbbrowser.cpp @@ -0,0 +1,40 @@ +#include + +#include "dbbrowser.h" +#include "log.h" +#include "sqlite3.h" +#include "utils.h" + +namespace DBBrowser { + int GetTables(std::vector &tables) { + sqlite3 *db = nullptr; + + int ret = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE, nullptr); + if (ret != SQLITE_OK) { + Log::Error("sqlite3_open_v2 failed to open %s\n", db_path); + return ret; + } + + std::string query = std::string("SELECT tbl_name FROM sqlite_schema WHERE type = 'table';"); + + sqlite3_stmt *stmt = nullptr; + ret = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr); + + while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { + char res[64]; + sceClibSnprintf(res, 64, "%s", sqlite3_column_text(stmt, 0)); + tables.push_back(res); + } + + if (ret != SQLITE_DONE) { + sqlite3_finalize(stmt); + sqlite3_close(db); + return ret; + } + + sqlite3_finalize(stmt); + sqlite3_close(db); + return 0; + } + +} diff --git a/dbbrowser.h b/dbbrowser.h new file mode 100644 index 0000000..6b9001c --- /dev/null +++ b/dbbrowser.h @@ -0,0 +1,11 @@ +#ifndef _VITA_HB_SORTER_DB_BROWSER_H_ +#define _VITA_HB_SORTER_DB_BROWSER_H_ + +#include +#include + +namespace DBBrowser { + int GetTables(std::vector &tables); +} + +#endif diff --git a/include/applist.h b/include/applist.h index 8a3631c..9404916 100644 --- a/include/applist.h +++ b/include/applist.h @@ -40,8 +40,6 @@ struct AppEntries { std::vector child_apps; }; -extern int sort_mode; - namespace AppList { int Get(AppEntries &entries); int Save(std::vector &entries); diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..8291c80 --- /dev/null +++ b/include/config.h @@ -0,0 +1,18 @@ +#ifndef _VITA_HB_SORTER_CONFIG_H_ +#define _VITA_HB_SORTER_CONFIG_H_ + +typedef struct { + bool beta_features = false; + int sort_by = 0; + int sort_folders = 0; + int sort_mode = 0; +} config_t; + +extern config_t cfg; + +namespace Config { + int Save(config_t &config); + int Load(void); +} + +#endif diff --git a/include/fs.h b/include/fs.h index 77013df..f664338 100644 --- a/include/fs.h +++ b/include/fs.h @@ -11,6 +11,7 @@ namespace FS { bool DirExists(const std::string &path); int CreateFile(const std::string &path); int MakeDir(const std::string &path); + int WriteFile(const std::string &path, const void *data, SceSize size); int RemoveFile(const std::string &path); int CopyFile(const std::string &src_path, const std::string &dest_path); std::string GetFileExt(const std::string &filename); diff --git a/source/applist.cpp b/source/applist.cpp index 1e48565..e6395eb 100644 --- a/source/applist.cpp +++ b/source/applist.cpp @@ -4,6 +4,7 @@ #include #include "applist.h" +#include "config.h" #include "fs.h" #include "log.h" #include "sqlite3.h" @@ -205,8 +206,8 @@ namespace AppList { } bool SortAppAsc(const AppInfoIcon &entryA, const AppInfoIcon &entryB) { - std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId; - std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId; + std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId; + std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId; std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); }); std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); }); @@ -218,8 +219,8 @@ namespace AppList { } bool SortAppDesc(const AppInfoIcon &entryA, const AppInfoIcon &entryB) { - std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId; - std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId; + std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId; + std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId; std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); }); std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); }); @@ -231,8 +232,8 @@ namespace AppList { } bool SortChildAppAsc(const AppInfoChild &entryA, const AppInfoChild &entryB) { - std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId; - std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId; + std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId; + std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId; std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); }); std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); }); @@ -244,8 +245,8 @@ namespace AppList { } bool SortChildAppDesc(const AppInfoChild &entryA, const AppInfoChild &entryB) { - std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId; - std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId; + std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId; + std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId; std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); }); std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); }); @@ -267,7 +268,7 @@ namespace AppList { } // App/Game belongs to a folder - if (entries.icons[i].pageNo < 0) { + if ((entries.icons[i].pageNo < 0) && (cfg.sort_folders == 0 || cfg.sort_folders == 2)) { for (unsigned int j = 0; j < entries.folders.size(); j++) { if (entries.icons[i].pageId == entries.folders[j].pageId) { entries.icons[i].pos = entries.folders[j].index; @@ -275,7 +276,7 @@ namespace AppList { } } } - else { + else if ((entries.icons[i].pageNo >= 0) && (cfg.sort_folders == 0 || cfg.sort_folders == 1)) { entries.icons[i].pos = pos; entries.icons[i].pageId = entries.pages[pageCounter].pageId; pos++; diff --git a/source/config.cpp b/source/config.cpp new file mode 100644 index 0000000..ca00ed0 --- /dev/null +++ b/source/config.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include + +#include "config.h" +#include "fs.h" +#include "log.h" +#include "utils.h" + +#define CONFIG_VERSION 1 + +config_t cfg; + +namespace Config { + static constexpr char config_path[] = "ux0:data/VITAHomebrewSorter/config.json"; + static const char *config_file = "{\n\t\"beta_features\": %s,\n\t\"sort_by\": %d,\n\t\"sort_folders\": %d,\n\t\"sort_mode\": %d,\n\t\"version\": %d\n}"; + static int config_version_holder = 0; + + class Allocator : public sce::Json::MemAllocator { + public: + Allocator() { + + } + + virtual void *allocateMemory(size_t size, void *unk) override { + return std::malloc(size); + } + + virtual void freeMemory(void *ptr, void *unk) override { + std::free(ptr); + } + }; + + int Save(config_t &config) { + int ret = 0; + std::unique_ptr buffer(new char[128]); + SceSize len = sceClibSnprintf(buffer.get(), 128, config_file, config.beta_features? "true" : "false", + config.sort_by, config.sort_folders, config.sort_mode, CONFIG_VERSION); + + if (R_FAILED(ret = FS::WriteFile(config_path, buffer.get(), len))) + return ret; + + return 0; + } + + int Load(void) { + int ret = 0; + + if (!FS::FileExists(config_path)) { + cfg = {0}; + return Config::Save(cfg); + } + + SceUID file = 0; + if (R_FAILED(ret = file = sceIoOpen(config_path, SCE_O_RDONLY, 0))) { + Log::Error("sceIoOpen(%s) failed: 0x%lx\n", config_path, ret); + return ret; + } + + SceSize size = sceIoLseek(file, 0, SCE_SEEK_END); + std::unique_ptr buffer(new char[size]); + + if (R_FAILED(ret = sceIoPread(file, buffer.get(), size, SCE_SEEK_SET))) { + Log::Error("sceIoRead(%s) failed: 0x%lx\n", config_path, ret); + sceIoClose(file); + return ret; + } + + if (R_FAILED(ret = sceIoClose(file))) { + Log::Error("sceIoClose(%s) failed: 0x%lx\n", config_path, ret); + return ret; + } + + Allocator *alloc = new Allocator(); + + sce::Json::InitParameter params; + params.allocator = alloc; + params.bufSize = size; + + sce::Json::Initializer init = sce::Json::Initializer(); + if (R_FAILED(ret = init.initialize(¶ms))) { + Log::Error("sce::Json::Initializer::initialize failed 0x%lx\n", ret); + init.terminate(); + delete alloc; + return ret; + } + + sce::Json::Value value = sce::Json::Value(); + if (R_FAILED(ret = sce::Json::Parser::parse(value, buffer.get(), params.bufSize))) { + Log::Error("sce::Json::Parser::parse failed 0x%lx\n", ret); + init.terminate(); + delete alloc; + return ret; + } + + // We know sceJson API loops through the child values in root alphabetically. + cfg.beta_features = value.getValue(0).getBoolean(); + cfg.sort_by = value.getValue(1).getInteger(); + cfg.sort_folders = value.getValue(2).getInteger(); + cfg.sort_mode = value.getValue(3).getInteger(); + config_version_holder = value.getValue(4).getInteger(); + + init.terminate(); + delete alloc; + + // Delete config file if config file is updated. This will rarely happen. + if (config_version_holder < CONFIG_VERSION) { + sceIoRemove(config_path); + cfg = {0}; + return Config::Save(cfg); + } + + return 0; + } +} diff --git a/source/fs.cpp b/source/fs.cpp index 54887bf..8a6cd3c 100644 --- a/source/fs.cpp +++ b/source/fs.cpp @@ -105,7 +105,7 @@ namespace FS { return bytes_read; } - static int WriteFile(const std::string &path, const void *data, SceSize size) { + int WriteFile(const std::string &path, const void *data, SceSize size) { int ret = 0, bytes_written = 0; SceUID file = 0; @@ -141,23 +141,34 @@ namespace FS { int CopyFile(const std::string &src_path, const std::string &dest_path) { int ret = 0; - std::unique_ptr buffer(new unsigned char[512]); + unsigned char *data = nullptr; SceOff size = 0; if (R_FAILED(ret = FS::GetFileSize(src_path, size))) return ret; + + data = new unsigned char[size]; + if (!data) + return -1; - if (R_FAILED(ret = FS::ReadFile(src_path, buffer.get(), size))) + if (R_FAILED(ret = FS::ReadFile(src_path, data, size))) { + delete[] data; return ret; + } if (FS::FileExists(dest_path)) { - if (R_FAILED(ret = FS::RemoveFile(dest_path))) + if (R_FAILED(ret = FS::RemoveFile(dest_path))) { + delete[] data; return ret; + } } - if (R_FAILED(ret = FS::WriteFile(dest_path, buffer.get(), size))) + if (R_FAILED(ret = FS::WriteFile(dest_path, data, size))) { + delete[] data; return ret; - + } + + delete[] data; return 0; } diff --git a/source/gui.cpp b/source/gui.cpp index 62c5885..4f4e364 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -5,6 +5,7 @@ #include #include "applist.h" +#include "config.h" #include "fs.h" #include "imgui_impl_vitagl.h" #define IMGUI_DEFINE_MATH_OPERATORS @@ -14,8 +15,6 @@ #include "textures.h" #include "utils.h" -int sort_mode = 0; - namespace Renderer { static void End(bool clear, ImVec4 clear_color) { glViewport(0, 0, static_cast(ImGui::GetIO().DisplaySize.x), static_cast(ImGui::GetIO().DisplaySize.y)); @@ -44,6 +43,17 @@ namespace GUI { }; enum SortBy { + SortTitle, + SortTitleID + }; + + enum SortFolders { + SortBoth, + SortAppsOnly, + SortFoldersOnly + }; + + enum SortMode { SortDefault, SortAsc, SortDesc @@ -58,6 +68,8 @@ namespace GUI { static bool backupExists = false; static const ImVec2 tex_size = ImVec2(20, 20); + static const char *sort_by[] = {"Title", "Title ID"}; + static const char *sort_folders[] = {"Both", "Apps only", "Folders only"}; static void SetupPopup(const char *id) { ImGui::OpenPopup(id); @@ -220,30 +232,66 @@ namespace GUI { GUI::ExitPopup(); } - static void SortTab(AppEntries &entries, SortBy &sort, State &state) { + static void SortTab(AppEntries &entries, State &state) { ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY; if (ImGui::BeginTabItem("Sort/Backup")) { ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing + + ImGui::PushID("sort_by"); + ImGui::PushItemWidth(100.f); + if (ImGui::BeginCombo("", sort_by[cfg.sort_by])) { + for (int i = 0; i < IM_ARRAYSIZE(sort_by); i++) { + const bool is_selected = (cfg.sort_by == i); + + if (ImGui::Selectable(sort_by[i], is_selected)) { + cfg.sort_by = i; + Config::Save(cfg); + } + + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::PopID(); + + ImGui::SameLine(); - ImGui::SetNextItemWidth(100.0f); - if (ImGui::Combo("Sort by", &sort_mode, "Title\0Title ID\0")) { - sort = SortDefault; + ImGui::PushID("sort_folders"); + ImGui::PushItemWidth(150.f); + if (ImGui::BeginCombo("", sort_folders[cfg.sort_folders])) { + for (int i = 0; i < IM_ARRAYSIZE(sort_folders); i++) { + const bool is_selected = (cfg.sort_folders == i); + + if (ImGui::Selectable(sort_folders[i], is_selected)) { + cfg.sort_folders = i; + Config::Save(cfg); + } + + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::PopID(); + + ImGui::SameLine(); + + if (ImGui::RadioButton("Default", cfg.sort_mode == SortDefault)) { + cfg.sort_mode = SortDefault; AppList::Get(entries); } ImGui::SameLine(); - if (ImGui::RadioButton("Default", sort == SortDefault)) { - sort = SortDefault; - AppList::Get(entries); - } - - ImGui::SameLine(); - - if (ImGui::RadioButton("Asc", sort == SortAsc)) { - sort = SortAsc; + if (ImGui::RadioButton("Asc", cfg.sort_mode == SortAsc)) { + cfg.sort_mode = SortAsc; AppList::Get(entries); std::sort(entries.icons.begin(), entries.icons.end(), AppList::SortAppAsc); std::sort(entries.child_apps.begin(), entries.child_apps.end(), AppList::SortChildAppAsc); @@ -252,8 +300,8 @@ namespace GUI { ImGui::SameLine(); - if (ImGui::RadioButton("Desc", sort == SortDesc)) { - sort = SortDesc; + if (ImGui::RadioButton("Desc", cfg.sort_mode == SortDesc)) { + cfg.sort_mode = SortDesc; AppList::Get(entries); std::sort(entries.icons.begin(), entries.icons.end(), AppList::SortAppDesc); std::sort(entries.child_apps.begin(), entries.child_apps.end(), AppList::SortChildAppDesc); @@ -262,10 +310,10 @@ namespace GUI { ImGui::SameLine(); - GUI::DisableButtonInit(sort == SortDefault); + GUI::DisableButtonInit(cfg.sort_mode == SortDefault); if (ImGui::Button("Apply Sort")) state = StateConfirm; - GUI::DisableButtonExit(sort == SortDefault); + GUI::DisableButtonExit(cfg.sort_mode == SortDefault); if (backupExists) { ImGui::SameLine(); @@ -422,9 +470,28 @@ namespace GUI { } } - static void AboutTab(void) { - if (ImGui::BeginTabItem("About")) { + static void SettingsTab(void) { + if (ImGui::BeginTabItem("Settings")) { ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing + + ImGui::Indent(5.f); + ImGui::TextColored(ImVec4(0.70f, 0.16f, 0.31f, 1.0f), "Beta features:"); + ImGui::Indent(15.f); + + if (ImGui::RadioButton("Enabled", cfg.beta_features == true)) { + cfg.beta_features = true; + Config::Save(cfg); + } + + ImGui::SameLine(); + + if (ImGui::RadioButton("Disabled", cfg.beta_features == false)) { + cfg.beta_features = false; + Config::Save(cfg); + } + + ImGui::Dummy(ImVec2(0.0f, 10.0f)); // Spacing + ImGui::Unindent(); ImGui::Indent(5.f); ImGui::TextColored(ImVec4(0.70f, 0.16f, 0.31f, 1.0f), "App Info:"); @@ -465,10 +532,7 @@ namespace GUI { FS::GetDirList("ux0:data/VITAHomebrewSorter/loadouts", loadouts); int date_format = Utils::GetDateFormat(); std::string loadout_name; - - SortBy sort = SortDefault; State state = StateNone; - SceCtrlData pad = { 0 }; while (!done) { @@ -478,11 +542,11 @@ namespace GUI { if (ImGui::Begin("VITA Homebrew Sorter", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) { if (ImGui::BeginTabBar("VITA Homebrew Sorter tabs")) { - GUI::SortTab(entries, sort, state); - GUI::DisableButtonInit(true); + GUI::SortTab(entries, state); + GUI::DisableButtonInit(cfg.beta_features? false : true); GUI::LoadoutsTab(loadouts, state, date_format, loadout_name); - GUI::DisableButtonExit(true); - GUI::AboutTab(); + GUI::DisableButtonExit(cfg.beta_features? false : true); + GUI::SettingsTab(); ImGui::EndTabBar(); } } diff --git a/source/main.cpp b/source/main.cpp index ba7cf0a..ec9d6bb 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,6 +1,7 @@ #include #include +#include "config.h" #include "fs.h" #include "gui.h" #include "imgui_impl_vitagl.h" @@ -85,7 +86,7 @@ namespace Services { SCE_CTRL_ENTER = Utils::GetEnterButton(); SCE_CTRL_CANCEL = Utils::GetCancelButton(); - sceSysmoduleLoadModule(SCE_SYSMODULE_SQLITE); + sceSysmoduleLoadModule(SCE_SYSMODULE_JSON); if (!FS::DirExists("ux0:data/VITAHomebrewSorter/backup")) FS::MakeDir("ux0:data/VITAHomebrewSorter/backup"); @@ -95,15 +96,15 @@ namespace Services { Log::Init(); Textures::Init(); - Power::InitThread(); + Config::Load(); } void Exit(void) { // Clean up Textures::Exit(); Log::Exit(); - sceSysmoduleUnloadModule(SCE_SYSMODULE_SQLITE); + sceSysmoduleUnloadModule(SCE_SYSMODULE_JSON); Utils::EndAppUtil(); ImGui_ImplVitaGL_Shutdown(); ImGui::DestroyContext();