From 34ade17556705d719871beefed4267f78edf79d7 Mon Sep 17 00:00:00 2001 From: StackZ <47382115+SuperSaiyajinStackZ@users.noreply.github.com> Date: Sat, 20 Jun 2020 23:17:28 +0200 Subject: [PATCH] WIP: Display if update is available on UniStore v2! Use `updates.json` for it in `sd:/3ds/Universal-Updater/`. --- assets/gfx/sprites.t3s | 1 + assets/gfx/sprites/updateStore.png | Bin 0 -> 964 bytes include/screens/unistore_v2.hpp | 6 +-- include/utils/store.hpp | 10 ++-- romfs/lang/en/app.json | 5 +- source/screens/unistore.cpp | 8 ++-- source/screens/unistore_v2.cpp | 39 +++++++++++---- source/utils/store.cpp | 73 ++++++++++++++++++++++++++++- 8 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 assets/gfx/sprites/updateStore.png diff --git a/assets/gfx/sprites.t3s b/assets/gfx/sprites.t3s index 2dd7c53..b5f7abc 100644 --- a/assets/gfx/sprites.t3s +++ b/assets/gfx/sprites.t3s @@ -16,6 +16,7 @@ sprites/side_arrow.png sprites/uniStore.png sprites/uniStore_HD.png sprites/update.png +sprites/updateStore.png sprites/view.png sprites/credits/discord.png diff --git a/assets/gfx/sprites/updateStore.png b/assets/gfx/sprites/updateStore.png new file mode 100644 index 0000000000000000000000000000000000000000..a1c4252645e703712323ae4bc6c9602a48d1e5ee GIT binary patch literal 964 zcmV;#13UbQP)EX>4Tx04R}tkv&MmKpe$i(~2S$2Rn#V2vVIah>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|}?mh0_0Yam~R5LIEsG4P@ z;xRFsTNS%s5kMFMhA|>BQ%|H9Gw>W=_we!cF3PjK&;2?2l)T9RpGZ8%bi*RvAfDN@ zbk6(4Ay$$U;&bA0gDyz?$aUG}H_ki6e@tQNECM zS>e3JS*_Gq>z@3D!MwJT<~q$V;#figNr;e9Lm3rVh|;Q&Vj@NRF%SQc<4=-HCRZ7Z z91EyIh2;3b|KNAGW?^d5O$x?7zMg_fo9#dzmILZc>?&Kfh(=;uQq_$Ptxmc zEpi0(Zvz+CZB5<-E_Z;zCtWfmNAlAY3I*W(jJ_!c4BP^}Yi@6?eVjf38R}~J1~@nb z#)_1^?(y!P&ffk#)9UXBe3x>oUdTaD00006VoOIv0002Q00d}=LHYmy010qNS#tmY zE+YT{E+YYWr9XB6000McNliruiPfx04j7vSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj27VtF7`Z**a7Y^8~;KG7*cI1;Vc6xEs#(uZ?Sl`P>?}J zV7&5j-RvroiQUK#rg(4eeQ##&9gnAB%@Obw_yp`%)hplv_{qV;?^U?BLfiws1K)T# zPJxhvhnzLCH9xRMhy6ft7R$|@7Ue>Vav?W&inCaU{Xkfw3cI)3-~?+FjzXogR7F`R z$rE*k9d(8sC3&KvER@btg`==;a`Nxa)#P-RD!a`TjiVa0T+ujEcAF`krK=}70&C8& zMsXIaD2p}nXgbp6-%Gu<@0Lx9vQV7GHRcSg8Dovy+$qVECAsUms-x?=%X*S0a&uSb zW51TF*=W+6_5k?x{+wVp06>UfH{j2QAppH;k7lD;zuvz2pH82zzrZz=AMXKp({DVh zsb86d$=!tZc6)u(^YJ}@t=8UdGr618>6KqOjKR%-)=rCP>-gDe(bh4oofd sortedStore; bool darkMode = true, sheetLoaded = false, canDisplay = false, hasLoaded = false, isDropDown = false; - int selectedBox = 0, lastViewMode = 0, dropSelection = 0, searchSelection = 0, iconAmount = 0, categorySelection = 0, selectedBoxList = 0, selection = -1, storePage = 0, downloadPage = 0, storePageList = 0, mode = 0, subSelection = 0, categoryPage = 0; + int selectedObject = 0, selectedBox = 0, lastViewMode = 0, dropSelection = 0, searchSelection = 0, iconAmount = 0, categorySelection = 0, selectedBoxList = 0, selection = -1, storePage = 0, downloadPage = 0, storePageList = 0, mode = 0, subSelection = 0, categoryPage = 0; nlohmann::json storeJson; C2D_SpriteSheet sheet; std::vector objects; @@ -64,7 +64,7 @@ private: void parseObjects(int selection); Result runFunctions(std::string entry); void DrawList(void) const; - void displaySelectedEntry(int selection) const; + void displaySelectedEntry(int selection, int storeIndex) const; void DropLogic(u32 hDown, u32 hHeld, touchPosition touch); void DropDownMenu(void) const; diff --git a/include/utils/store.hpp b/include/utils/store.hpp index 5f3b02f..5000fb4 100644 --- a/include/utils/store.hpp +++ b/include/utils/store.hpp @@ -41,6 +41,7 @@ struct UniStoreV2Struct { std::string last_updated; int icon_index; int JSONIndex; + bool updateAvailable; }; enum class SortType { @@ -51,8 +52,9 @@ enum class SortType { class Store { public: - Store(nlohmann::json &JS); + Store(nlohmann::json &JS, std::string updateJSON = "NOT_FOUND"); + void writeToFile(int index); void sorting(bool Ascending, SortType sorttype); std::string returnTitle(const int index); @@ -61,13 +63,14 @@ public: int returnJSONIndex(const int index); int getSize(); bool getAscending() { return this->ascending; } + bool isUpdateAvailable(int index) { return this->sortedStore[index].updateAvailable; } // Searching stuff. int searchForEntries(const std::string searchResult); int searchForAuthor(const std::string searchResult); int searchForCategory(const std::string searchResult); int searchForConsole(const std::string searchResult); - + bool updateAvailable(int index); void reset() { this->sortedStore = this->unsortedStore; } const int getSortType() { @@ -82,8 +85,9 @@ public: private: std::vector sortedStore, unsortedStore; std::vector availableCategories; + std::string updateFile; bool ascending = false; - nlohmann::json storeJson; + nlohmann::json storeJson, updateJSON; SortType sorttype = SortType::TITLE; UniStoreV2Struct getData(const int index); diff --git a/romfs/lang/en/app.json b/romfs/lang/en/app.json index d3c7ade..9e6242b 100644 --- a/romfs/lang/en/app.json +++ b/romfs/lang/en/app.json @@ -206,5 +206,8 @@ "CONSOLE_SEARCH": "Console search", "SEARCHING_FOR": "Searching for...", "SELECT_CATEGORY": "Select a category you like to view.", - "NO_CATEGORIES_AVAILABLE": "No categories available." + "NO_CATEGORIES_AVAILABLE": "No categories available.", + "UPDATE_AVAILABLE": "Update available!", + "UPDATE_NOT_AVAILABLE": "No updates available.", + "ENTRY_AMOUNT": "There are %i entries available." } diff --git a/source/screens/unistore.cpp b/source/screens/unistore.cpp index ff534a2..5bea6d1 100644 --- a/source/screens/unistore.cpp +++ b/source/screens/unistore.cpp @@ -78,7 +78,7 @@ void UniStore::autobootLogic() { if (storeInfo[0].version == 0 || storeInfo[0].version == 1) { Gui::setScreen(std::make_unique(JSON, sheetURL, displayInformations), config->screenFade(), true); } else if (storeInfo[0].version == 2) { - Gui::setScreen(std::make_unique(JSON, sheetURL), config->screenFade(), true); + Gui::setScreen(std::make_unique(JSON, sheetURL, currentStoreFile), config->screenFade(), true); } else { Msg::DisplayWarnMsg(Lang::get("UNISTORE_NOT_SUPPORTED")); } @@ -596,7 +596,7 @@ void UniStore::StoreSelectionLogic(u32 hDown, u32 hHeld, touchPosition touch) { if (storeInfo[Selection].version == 0 || storeInfo[Selection].version == 1) { Gui::setScreen(std::make_unique(JSON, sheetURL, displayInformations), config->screenFade(), true); } else if (storeInfo[Selection].version == 2) { - Gui::setScreen(std::make_unique(JSON, sheetURL), config->screenFade(), true); + Gui::setScreen(std::make_unique(JSON, sheetURL, currentStoreFile), config->screenFade(), true); } else { Msg::DisplayWarnMsg(Lang::get("UNISTORE_NOT_SUPPORTED")); } @@ -632,7 +632,7 @@ void UniStore::StoreSelectionLogic(u32 hDown, u32 hHeld, touchPosition touch) { if (storeInfo[screenPos + i].version == 0 || storeInfo[screenPos + i].version == 1) { Gui::setScreen(std::make_unique(JSON, sheetURL, displayInformations), config->screenFade(), true); } else if (storeInfo[screenPos + i].version == 2) { - Gui::setScreen(std::make_unique(JSON, sheetURL), config->screenFade(), true); + Gui::setScreen(std::make_unique(JSON, sheetURL, currentStoreFile), config->screenFade(), true); } else { Msg::DisplayWarnMsg(Lang::get("UNISTORE_NOT_SUPPORTED")); } @@ -653,7 +653,7 @@ void UniStore::StoreSelectionLogic(u32 hDown, u32 hHeld, touchPosition touch) { if (storeInfo[screenPosList + i].version == 0 || storeInfo[screenPosList + i].version == 1) { Gui::setScreen(std::make_unique(JSON, sheetURL, displayInformations), config->screenFade(), true); } else if (storeInfo[screenPosList + i].version == 2) { - Gui::setScreen(std::make_unique(JSON, sheetURL), config->screenFade(), true); + Gui::setScreen(std::make_unique(JSON, sheetURL, currentStoreFile), config->screenFade(), true); } else { Msg::DisplayWarnMsg(Lang::get("UNISTORE_NOT_SUPPORTED")); } diff --git a/source/screens/unistore_v2.cpp b/source/screens/unistore_v2.cpp index a229249..cd6ce0a 100644 --- a/source/screens/unistore_v2.cpp +++ b/source/screens/unistore_v2.cpp @@ -40,9 +40,9 @@ extern bool touching(touchPosition touch, Structs::ButtonPos button); #define DOWNLOAD_ENTRIES 5 extern bool didAutoboot; -UniStoreV2::UniStoreV2(nlohmann::json &JSON, const std::string sheetPath) { +UniStoreV2::UniStoreV2(nlohmann::json &JSON, const std::string sheetPath, const std::string fileName) { this->storeJson = JSON; - this->sortedStore = std::make_unique(this->storeJson); + this->sortedStore = std::make_unique(this->storeJson, fileName); if (access(sheetPath.c_str(), F_OK) != 0) { this->iconAmount = 0; @@ -141,7 +141,7 @@ void UniStoreV2::DrawGrid(void) const { int offset2 = (48 - temp.subtex->height) / 2; Gui::DrawSprite(this->sheet, this->sortedStore->returnIconIndex(i + (this->storePage * STORE_ENTRIES)), this->StoreBoxesGrid[i].x+1 + offset, this->StoreBoxesGrid[i].y+1 + offset2); } else { - GFX::DrawSprite(sprites_noIcon_idx, this->StoreBoxesList[i].x+1, this->StoreBoxesList[i].y+1); + GFX::DrawSprite(sprites_noIcon_idx, this->StoreBoxesGrid[i].x+1, this->StoreBoxesGrid[i].y+1); } temp = {nullptr, nullptr}; } else { @@ -153,6 +153,10 @@ void UniStoreV2::DrawGrid(void) const { } else { GFX::DrawSprite(sprites_noIcon_idx, this->StoreBoxesGrid[i].x+1, this->StoreBoxesGrid[i].y+1); } + + if (this->sortedStore->isUpdateAvailable(i + (this->storePage * STORE_ENTRIES))) { + GFX::DrawSprite(sprites_updateStore_idx, this->StoreBoxesGrid[i].x+35, this->StoreBoxesGrid[i].y+35); + } } } @@ -186,8 +190,11 @@ void UniStoreV2::DrawList(void) const { GFX::DrawSprite(sprites_noIcon_idx, this->StoreBoxesList[i].x+1, this->StoreBoxesList[i].y+1); } + if (this->sortedStore->isUpdateAvailable(i + (this->storePageList * STORE_ENTRIES_LIST))) { + GFX::DrawSprite(sprites_updateStore_idx, this->StoreBoxesList[i].x+340, this->StoreBoxesList[i].y+30); + } + // Display Author & App name. - Gui::DrawString(this->StoreBoxesList[i].x+55, this->StoreBoxesList[i].y+12, 0.45f, this->returnTextColor(), this->sortedStore->returnTitle(i + (this->storePageList * STORE_ENTRIES_LIST)), 300); Gui::DrawString(this->StoreBoxesList[i].x+55, this->StoreBoxesList[i].y+28, 0.45f, this->returnTextColor(), this->sortedStore->returnAuthor(i + (this->storePageList * STORE_ENTRIES_LIST)), 300); } @@ -274,7 +281,7 @@ void UniStoreV2::DropDownMenu(void) const { } } -void UniStoreV2::displaySelectedEntry(int selection) const { +void UniStoreV2::displaySelectedEntry(int selection, int storeIndex) const { this->DrawBaseTop(); Gui::DrawStringCentered(0, 218, 0.7f, this->returnTextColor(), std::to_string(this->downloadPage + 1) + " | " + std::to_string(1 + (this->objects.size() / DOWNLOAD_ENTRIES))); @@ -329,6 +336,8 @@ void UniStoreV2::displaySelectedEntry(int selection) const { Gui::DrawStringCentered(0, 140, 0.5f, this->returnTextColor(), Lang::get("DESC") + "?", 400); } + Gui::DrawStringCentered(0, 170, 0.5f, this->returnTextColor(), this->sortedStore->isUpdateAvailable(storeIndex) ? Lang::get("UPDATE_AVAILABLE") : Lang::get("UPDATE_NOT_AVAILABLE"), 400); + this->DrawBaseBottom(); if (this->objects.size() > 0) { @@ -363,6 +372,9 @@ void UniStoreV2::Draw(void) const { if (fadealpha > 0) Gui::Draw_Rect(0, 0, 400, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); this->DrawBaseBottom(); + char entryAmount [150]; + snprintf(entryAmount, sizeof(entryAmount), Lang::get("ENTRY_AMOUNT").c_str(), this->sortedStore->getSize()); + Gui::DrawStringCentered(0, 0, 0.6f, this->returnTextColor(), entryAmount, 300); this->DrawSortingMenu(); if (fadealpha > 0) Gui::Draw_Rect(0, 0, 320, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); @@ -381,9 +393,12 @@ void UniStoreV2::Draw(void) const { if (fadealpha > 0) Gui::Draw_Rect(0, 0, 400, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); this->DrawBaseBottom(); this->DrawSortingMenu(); + char entryAmount [150]; + snprintf(entryAmount, sizeof(entryAmount), Lang::get("ENTRY_AMOUNT").c_str(), this->sortedStore->getSize()); + Gui::DrawStringCentered(0, 0, 0.6f, this->returnTextColor(), entryAmount, 300); if (fadealpha > 0) Gui::Draw_Rect(0, 0, 320, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); } else if (this->mode == 2) { - this->displaySelectedEntry(this->selection); + this->displaySelectedEntry(this->selection, this->selectedObject); } else if (this->mode == 3) { this->DrawSearchMenu(); } else if (this->mode == 4) { @@ -561,6 +576,7 @@ void UniStoreV2::Logic(u32 hDown, u32 hHeld, touchPosition touch) { if (hDown & KEY_A) { if (this->sortedStore->returnJSONIndex(this->selectedBox + (this->storePage * STORE_ENTRIES)) < (int)this->storeJson.at("storeContent").size()) { + this->selectedObject = this->selectedBox + (this->storePage * STORE_ENTRIES); this->selection = this->sortedStore->returnJSONIndex(this->selectedBox + (this->storePage * STORE_ENTRIES)); this->parseObjects(this->selection); this->canDisplay = true; @@ -619,6 +635,7 @@ void UniStoreV2::Logic(u32 hDown, u32 hHeld, touchPosition touch) { if (hDown & KEY_A) { if (this->sortedStore->returnJSONIndex(this->selectedBoxList + (this->storePageList * STORE_ENTRIES_LIST)) < (int)this->storeJson.at("storeContent").size()) { + this->selectedObject = this->selectedBoxList + (this->storePageList * STORE_ENTRIES_LIST); this->selection = this->sortedStore->returnJSONIndex(this->selectedBoxList + (this->storePageList * STORE_ENTRIES_LIST)); this->parseObjects(this->selection); this->canDisplay = true; @@ -646,7 +663,10 @@ void UniStoreV2::Logic(u32 hDown, u32 hHeld, touchPosition touch) { if (this->objects.size() > 0) { for (int i = 0, i2 = 0 + (this->downloadPage * DOWNLOAD_ENTRIES); i2 < DOWNLOAD_ENTRIES + (this->downloadPage * DOWNLOAD_ENTRIES) && i2 < (int)this->objects.size(); i2++, i++) { if (touching(touch, downloadBoxes[i])) { - if (Msg::promptMsg(Lang::get("EXECUTE_SCRIPT") + "\n" + this->objects[i + (this->downloadPage * DOWNLOAD_ENTRIES)])) runFunctions(this->objects[i + (this->downloadPage * DOWNLOAD_ENTRIES)]); + if (Msg::promptMsg(Lang::get("EXECUTE_SCRIPT") + "\n" + this->objects[i + (this->downloadPage * DOWNLOAD_ENTRIES)])) { + runFunctions(this->objects[i + (this->downloadPage * DOWNLOAD_ENTRIES)]); + this->sortedStore->writeToFile(this->selectedObject); + } } } } @@ -655,7 +675,10 @@ void UniStoreV2::Logic(u32 hDown, u32 hHeld, touchPosition touch) { if (hDown & KEY_A) { if (this->objects.size() > 0) { if ((int)this->objects.size() >= this->subSelection) { - if (Msg::promptMsg(Lang::get("EXECUTE_SCRIPT") + "\n" + this->objects[this->subSelection])) runFunctions(this->objects[this->subSelection]); + if (Msg::promptMsg(Lang::get("EXECUTE_SCRIPT") + "\n" + this->objects[this->subSelection])) { + runFunctions(this->objects[this->subSelection]); + this->sortedStore->writeToFile(this->selectedObject); + } } } } diff --git a/source/utils/store.cpp b/source/utils/store.cpp index 4be3ee3..10bb5ee 100644 --- a/source/utils/store.cpp +++ b/source/utils/store.cpp @@ -25,22 +25,88 @@ */ #include "store.hpp" +#include +#include -Store::Store(nlohmann::json &JS) { +Store::Store(nlohmann::json &JS, std::string JSONName) { this->storeJson = JS; + this->updateFile = JSONName; + + if (access("sdmc:/3ds/Universal-Updater/updates.json", F_OK) != 0) { + // We'd create the file here. + FILE *file = fopen("sdmc:/3ds/Universal-Updater/updates.json", "w"); + this->updateJSON = nlohmann::json::parse("{}"); // So we have a valid JSON at the end. + fwrite(this->updateJSON.dump(1, '\t').c_str(), 1, this->updateJSON.dump(1, '\t').size(), file); + fclose(file); + + FILE *file2 = fopen("sdmc:/3ds/Universal-Updater/updates.json", "r"); + this->updateJSON = nlohmann::json::parse(file2, nullptr, false); + fclose(file2); + } else { + FILE *file = fopen("sdmc:/3ds/Universal-Updater/updates.json", "r"); + this->updateJSON = nlohmann::json::parse(file, nullptr, false); + fclose(file); + } + + for (int i = 0; i < (int)this->storeJson.at("storeContent").size(); i++) { this->unsortedStore.push_back(this->getData(i)); } this->sortedStore = this->unsortedStore; // Put that to sorted store as well. + + // If Categories available, push them to our vector. if (this->storeJson.at("storeInfo").contains("categories")) { this->availableCategories = this->storeJson["storeInfo"]["categories"].get>(); } } +bool Store::updateAvailable(int index) { + if (index > (int)this->storeJson.at("storeContent").size()) return false; // out of scope. + if (this->storeJson.at("storeContent").at(index).at("info").contains("last_updated")) { + const std::string updateEntry = this->storeJson.at("storeContent").at(index).at("info").at("last_updated"); + const std::string entry = this->storeJson.at("storeContent").at(index).at("info").at("title"); + + if (this->updateJSON.contains(this->updateFile)) { + if (this->updateJSON.at(this->updateFile).contains(entry)) { + const std::string updateEntry2 = (std::string)this->updateJSON.at(this->updateFile).at(entry); + return strcasecmp(updateEntry.c_str(), updateEntry2.c_str()) > 0; + } else { + return true; // Since we do not have this entry there yet. + } + } else { // Our update json don't have that yet.. so display available. + return true; + } + } else { // Since the Store doesn't have that feature. + return false; + } + + return false; +} + +// Here we write that to our file. +void Store::writeToFile(int index) { + std::string timeString; + + time_t rawtime; + struct tm * ptm; + time (&rawtime); + ptm = gmtime (&rawtime); + + timeString = std::to_string(ptm->tm_year + 1900) + "-" + std::to_string(ptm->tm_mon + 1) + "-" + std::to_string(ptm->tm_mday) + " at " + std::to_string(ptm->tm_hour) + ":" + + std::to_string(ptm->tm_min) + " (UTC)"; + + FILE *file = fopen("sdmc:/3ds/Universal-Updater/updates.json", "w"); + this->updateJSON[this->updateFile][this->sortedStore[index].title] = timeString; + fwrite(this->updateJSON.dump(1, '\t').c_str(), 1, this->updateJSON.dump(1, '\t').size(), file); + fclose(file); + + this->sortedStore[index].updateAvailable = false; +} + // Here we get the data of the UniStore! UniStoreV2Struct Store::getData(const int index) { - UniStoreV2Struct temp = {"", "", "", "", "" ,"", -1, 0}; + UniStoreV2Struct temp = {"", "", "", "", "" ,"", -1, 0, false}; if (index > (int)this->storeJson.at("storeContent").size()) return temp; // Empty. @@ -79,6 +145,9 @@ UniStoreV2Struct Store::getData(const int index) { temp.icon_index = this->storeJson.at("storeContent").at(index).at("info").at("icon_index"); } + // Update available(?). + temp.updateAvailable = this->updateAvailable(index); + // JSON index. temp.JSONIndex = index;