diff --git a/Makefile b/Makefile index e893565..3755419 100644 --- a/Makefile +++ b/Makefile @@ -42,10 +42,10 @@ BUILD := build SOURCES := source DATA := data INCLUDES := include -ROMFS := romfs +#ROMFS := romfs APP_TITLE := Amiigo APP_AUTHOR := CompSciOrBust -APP_VERSION := 1.3.1 +APP_VERSION := 1.4.0 #--------------------------------------------------------------------------------- # options for code generation diff --git a/include/Networking.h b/include/Networking.h index f991f8e..be6e62d 100644 --- a/include/Networking.h +++ b/include/Networking.h @@ -3,6 +3,8 @@ #include std::size_t CurlStrWrite(const char* in, std::size_t size, std::size_t num, std::string* out); +std::size_t CurlFileWrite(const char* in, std::size_t size, std::size_t num, FILE* out); std::string RetrieveContent(std::string URL, std::string MIMEType); +void RetrieveToFile(std::string, std::string); std::string FormatURL(std::string TextToFormat); bool HasConnection(); \ No newline at end of file diff --git a/include/UI.h b/include/UI.h index c909d4e..31baab2 100644 --- a/include/UI.h +++ b/include/UI.h @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; class ScrollList @@ -26,4 +27,6 @@ class ScrollList bool CenterText = false; }; -bool CheckButtonPressed(SDL_Rect*, int, int); \ No newline at end of file +bool CheckButtonPressed(SDL_Rect*, int, int); +TTF_Font *GetSharedFont(int FontSize); +void DrawButtonBorders(SDL_Renderer*, ScrollList*, ScrollList*, int, int, int, int, bool); \ No newline at end of file diff --git a/include/UpdaterUI.h b/include/UpdaterUI.h new file mode 100644 index 0000000..f714321 --- /dev/null +++ b/include/UpdaterUI.h @@ -0,0 +1,31 @@ +//A lot of code for this was modified from Goldleaf. Thank you Xor (Again). +#include +#include +//#include +#include "networking.h" +#include "nlohmann/json.hpp" +#include +using namespace std; +using json = nlohmann::json; + +class UpdaterUI +{ + private: + void DrawText(std::string); + int UpdateState = 0; + bool CheckForNewVersion(); + std::string UpdateText = "null"; + TTF_Font *TextFont; + SDL_Color TextColour = {0, 0, 0}; + json GitAPIData; + string latestid; + public: + UpdaterUI(); + void DrawUI(); + SDL_Event *Event; + int *WindowState; + SDL_Renderer *renderer; + int *Width; + int *Height; + int *IsDone; +}; \ No newline at end of file diff --git a/source/AmiigoUI.cpp b/source/AmiigoUI.cpp index b2abe7e..892d738 100644 --- a/source/AmiigoUI.cpp +++ b/source/AmiigoUI.cpp @@ -41,13 +41,16 @@ class AmiigoUI AmiigoUI::AmiigoUI() { //Load the header font - HeaderFont = TTF_OpenFont("romfs:/font.ttf", 48); + //HeaderFont = TTF_OpenFont("romfs:/font.ttf", 48); + HeaderFont = GetSharedFont(48); + //Create the lists AmiiboList = new ScrollList(); MenuList = new ScrollList(); //Add items to the menu list MenuList->ListingTextVec.push_back("Amiibo list"); MenuList->ListingTextVec.push_back("Amiigo Maker"); + MenuList->ListingTextVec.push_back("Check for updates"); MenuList->ListingTextVec.push_back("Exit"); //Scan the Amiibo folder for Amiibos ScanForAmiibos(); @@ -164,11 +167,6 @@ void AmiigoUI::DrawUI() *WindowState = MenuList->SelectedIndex; } } - //B pressed so switch to Amiibo generator - else if(Event->jbutton.button == 1) - { - *WindowState = 1; - } } break; } @@ -184,6 +182,8 @@ void AmiigoUI::DrawUI() DrawFooter(); AmiiboList->DrawList(); MenuList->DrawList(); + DrawButtonBorders(renderer, AmiiboList, MenuList, HeaderHeight, FooterHeight, *Width, *Height, false); + //DrawButtonBorders(); //Check if list item selected via touch screen if(AmiiboList->ItemSelected) { @@ -340,10 +340,14 @@ void AmiigoUI::InitList() HeaderHeight = (*Height / 100) * 10; FooterHeight = (*Height / 100) * 10; AmiiboListWidth = (*Width / 100) * 80; + //for shared font + PlFontData standardFontData; + plGetSharedFontByType(&standardFontData, PlSharedFontType_Standard); + //Assign vars AmiiboList->TouchListX = &TouchX; AmiiboList->TouchListY = &TouchY; - AmiiboList->ListFont = TTF_OpenFont("romfs:/font.ttf", 32); //Load the list font + AmiiboList->ListFont = GetSharedFont(32); //Load the list font AmiiboList->ListingsOnScreen = 10; AmiiboList->ListHeight = *Height - HeaderHeight - FooterHeight; AmiiboList->ListWidth = AmiiboListWidth; @@ -358,8 +362,8 @@ void AmiigoUI::InitList() //Menu list MenuList->TouchListX = &TouchX; MenuList->TouchListY = &TouchY; - MenuList->ListFont = TTF_OpenFont("romfs:/font.ttf", 32); //Load the list font - MenuList->ListingsOnScreen = 3; + MenuList->ListFont = GetSharedFont(32); //Load the list font + MenuList->ListingsOnScreen = MenuList->ListingTextVec.size(); MenuList->ListHeight = *Height - HeaderHeight - FooterHeight; MenuList->ListWidth = *Width - AmiiboListWidth; MenuList->ListYOffset = HeaderHeight; diff --git a/source/CreatorUI.cpp b/source/CreatorUI.cpp index 57a2663..b9a5372 100644 --- a/source/CreatorUI.cpp +++ b/source/CreatorUI.cpp @@ -63,8 +63,10 @@ class CreatorUI CreatorUI::CreatorUI() { nifmInitialize(); //Init nifm for connection stuff - HeaderFont = TTF_OpenFont("romfs:/font.ttf", 48); //Load the header font - ListFont = TTF_OpenFont("romfs:/font.ttf", 32); //Load the list font + HeaderFont = GetSharedFont(48); + ListFont = GetSharedFont(32); + //HeaderFont = TTF_OpenFont("romfs:/font.ttf", 48); //Load the header font + //ListFont = TTF_OpenFont("romfs:/font.ttf", 32); //Load the list font GetDataFromAPI(""); //Get data from the API //Create the lists @@ -104,7 +106,7 @@ void CreatorUI::InitList() //Create the lists SeriesList->TouchListX = &TouchX; SeriesList->TouchListY = &TouchY; - SeriesList->ListFont = TTF_OpenFont("romfs:/font.ttf", 32); //Load the list font + SeriesList->ListFont = GetSharedFont(32); //Load the list font SeriesList->ListingsOnScreen = 10; SeriesList->ListWidth = SeriesListWidth; SeriesList->renderer = renderer; @@ -212,6 +214,7 @@ void CreatorUI::DrawUI() SeriesList->DrawList(); MenuList->DrawList(); DrawFooter(); + DrawButtonBorders(renderer, SeriesList, MenuList, HeaderHeight, FooterHeight, *Width, *Height, true); //Check if list item selected via touch screen if(SeriesList->ItemSelected) { diff --git a/source/Networking.cpp b/source/Networking.cpp index 1477bb8..64db114 100644 --- a/source/Networking.cpp +++ b/source/Networking.cpp @@ -10,6 +10,13 @@ std::size_t CurlStrWrite(const char* in, std::size_t size, std::size_t num, std: out->append(in, totalBytes); return totalBytes; } + +std::size_t CurlFileWrite(const char* in, std::size_t size, std::size_t num, FILE* out) +{ + fwrite(in, size, num, out); + return (size * num); +} + std::string RetrieveContent(std::string URL, std::string MIMEType) { socketInitializeDefault(); @@ -23,6 +30,7 @@ std::string RetrieveContent(std::string URL, std::string MIMEType) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerdata); } curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "Amiigo"); //Turns out this was important and I should not have deleted it curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); @@ -34,6 +42,28 @@ std::string RetrieveContent(std::string URL, std::string MIMEType) return cnt; } +void RetrieveToFile(std::string URL, std::string Path) +{ + socketInitializeDefault(); + FILE *f = fopen(Path.c_str(), "wb"); + if(f) + { + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, URL.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "Amiigo"); + 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, CurlFileWrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_perform(curl); + curl_easy_cleanup(curl); + } + fclose(f); + socketExit(); +} + //I made this so even though it's only one two calls it's probably janky. std::string FormatURL(std::string TextToFormat) { diff --git a/source/UI.cpp b/source/UI.cpp index 300ab5d..f23c916 100644 --- a/source/UI.cpp +++ b/source/UI.cpp @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; class ScrollList @@ -131,4 +132,46 @@ void ScrollList::DrawList() SDL_DestroyTexture(FileNameTexture); SDL_FreeSurface(FileNameSurface); } +} + +//Thank you to Nichole Mattera for telling me how to do this +TTF_Font *GetSharedFont(int FontSize) +{ + PlFontData standardFontData; + plGetSharedFontByType(&standardFontData, PlSharedFontType_Standard); + return TTF_OpenFontRW(SDL_RWFromMem(standardFontData.address, standardFontData.size), 1, FontSize); +} + +void DrawButtonBorders(SDL_Renderer* renderer, ScrollList *LeftList, ScrollList *MenuList, int HeaderHeight, int FooterHeight, int Width, int Height, bool SplitFooter) +{ + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + int BorderSize = 3; + //Draw border for the two lists + SDL_Rect BorderRect = {MenuList->ListXOffset, MenuList->ListYOffset, BorderSize, MenuList->ListHeight}; + SDL_RenderFillRect(renderer, &BorderRect); + //Draw border for the header + BorderRect = {0, HeaderHeight, Width, BorderSize}; + SDL_RenderFillRect(renderer, &BorderRect); + //Draw the menu list border + for(int i = 0; i < MenuList->ListingsOnScreen; i++) + { + int MenuListButtonSize = MenuList->ListHeight / MenuList->ListingsOnScreen; + SDL_Rect BorderRect = {MenuList->ListXOffset, MenuList->ListYOffset + (i * MenuListButtonSize), MenuList->ListWidth, BorderSize}; + SDL_RenderFillRect(renderer, &BorderRect); + } + //Draw the left list border + for(int i = 1; i < LeftList->ListingsOnScreen; i++) + { + int MenuListButtonSize = LeftList->ListHeight / LeftList->ListingsOnScreen; + SDL_Rect BorderRect = {0, LeftList->ListYOffset + (i * MenuListButtonSize) - 1, LeftList->ListWidth, BorderSize}; + SDL_RenderFillRect(renderer, &BorderRect); + if(LeftList->ListingTextVec.size() == i) break; + } + //Draw the footer border + BorderRect = {0, Height - FooterHeight, Width, BorderSize}; + SDL_RenderFillRect(renderer, &BorderRect); + if(!SplitFooter) return; + //Draw the footer button border + BorderRect = {Width/2, Height - FooterHeight, BorderSize, Height}; + SDL_RenderFillRect(renderer, &BorderRect); } \ No newline at end of file diff --git a/source/UpdaterUI.cpp b/source/UpdaterUI.cpp new file mode 100644 index 0000000..507d735 --- /dev/null +++ b/source/UpdaterUI.cpp @@ -0,0 +1,138 @@ +//A lot of code for this was modified from Goldleaf. Thank you Xor (Again). +#include +#include +#include +#include "networking.h" +#include "nlohmann/json.hpp" +#include +using namespace std; +using json = nlohmann::json; + +class UpdaterUI +{ + private: + void DrawText(std::string); + int UpdateState = 0; + bool CheckForNewVersion(); + std::string UpdateText = " "; + TTF_Font *TextFont; + SDL_Color TextColour = {0, 0, 0}; + json GitAPIData; + string LatestID; + public: + UpdaterUI(); + void DrawUI(); + SDL_Event *Event; + int *WindowState; + SDL_Renderer *renderer; + int *Width; + int *Height; + int *IsDone; +}; + +UpdaterUI::UpdaterUI() +{ + nifmInitialize(); //Init nifm for connection stuff + TextFont = GetSharedFont(48); +} + +void UpdaterUI::DrawUI() +{ + //Handle input + bool BPressed = false; + while (SDL_PollEvent(Event)) + { + switch (Event->type) + { + case SDL_JOYBUTTONDOWN: + if (Event->jbutton.which == 0) + { + //Plus pressed + if (Event->jbutton.button == 10) + { + *IsDone = 1; + } + //B pressed + else if(Event->jbutton.button == 1) + { + BPressed = true; + } + } + } + break; + } + + //Do the update stages + switch(UpdateState) + { + //Check connection stage + case 0: + { + //Check we have a connection before trying to access the network + if(HasConnection()) + { + UpdateState++; + } + else + { + UpdateText = "Waiting for connection."; + } + } + break; + //Check if newer version stage + case 1: + { + if(CheckForNewVersion()) + { + UpdateState++; + UpdateText = "Downloading " + LatestID + ". This might take a while."; + } + else + { + UpdateText = "Already on the latest version."; + if(BPressed) + { + *WindowState = 0; + } + } + } + break; + //Download update stage + case 2: + { + string UpdateFileURL = "https://github.com/CompSciOrBust/Amiigo/releases/download/" + LatestID + "/Amiigo.nro"; + RetrieveToFile(UpdateFileURL, "sdmc:/switch/Failed_Amiigo_Update.nro"); + remove("sdmc:/switch/Amiigo.nro"); + rename("sdmc:/switch/Failed_Amiigo_Update.nro", "sdmc:/switch/Amiigo.nro"); + *IsDone = 1; + } + break; + } + DrawText(UpdateText); +} + +bool UpdaterUI::CheckForNewVersion() +{ + //Get data from GitHub API + string Data = RetrieveContent("https://api.github.com/repos/CompSciOrBust/Amiigo/releases", "application/json"); + GitAPIData = json::parse(Data); + LatestID = GitAPIData[0]["tag_name"].get(); + //Check if we're running the latest version + return (LatestID != APP_VERSION); +} + +void UpdaterUI::DrawText(std::string Message) +{ + //Draw the rect + SDL_SetRenderDrawColor(renderer, 0, 188, 212, 255); + SDL_Rect MessageRect = {0,0, *Width, *Height}; + SDL_RenderFillRect(renderer, &MessageRect); + //Draw the text + SDL_Surface* MessageTextSurface = TTF_RenderUTF8_Blended_Wrapped(TextFont, Message.c_str(), TextColour, *Width); + SDL_Texture* MessagerTextTexture = SDL_CreateTextureFromSurface(renderer, MessageTextSurface); + SDL_Rect HeaderTextRect = {(*Width - MessageTextSurface->w) / 2, (*Height - MessageTextSurface->h) / 2, MessageTextSurface->w, MessageTextSurface->h}; + SDL_RenderCopy(renderer, MessagerTextTexture, NULL, &HeaderTextRect); + //Clean up + SDL_DestroyTexture(MessagerTextTexture); + SDL_FreeSurface(MessageTextSurface); +} \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index 56f2cea..9460378 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include int main(int argc, char *argv[]) { @@ -59,10 +60,10 @@ int main(int argc, char *argv[]) } } - romfsInit(); //Init romfs to load font TTF_Init(); //Init the font nfpemuInitialize(); //Init nfp ipc - + plInitialize(); //Init needed for shared font + //Give MainUI access to vars AmiigoUI *MainUI = new AmiigoUI(); MainUI->Event = &event; @@ -74,6 +75,7 @@ int main(int argc, char *argv[]) MainUI->InitList(); CreatorUI *AmiigoGenUI = NULL; + UpdaterUI *UpUI = NULL; //Make the amiibo folder in case it doesn't exist //Not if it exists checking first feels dirty but it doesn't error out. Should we check anyway? @@ -92,7 +94,7 @@ int main(int argc, char *argv[]) { MainUI->DrawUI(); //If the user has switched to the maker UI and the data isn't read show the please wait message - if(AmiigoGenUI == NULL && WindowState != 0) + if(AmiigoGenUI == NULL && WindowState == 1) { //Display the please wait message MainUI->PleaseWait(); @@ -120,24 +122,42 @@ int main(int argc, char *argv[]) //Render the UI AmiigoGenUI->DrawUI(); //If the window state has changed then we need to rescan the amiibo folder to load the new amiibos in to the list - if(WindowState != 1) + if(WindowState == 0) { MainUI->ScanForAmiibos(); } } break; + //Draw the Amiigo updater + case 2: + { + //Check if the UI has been initialized + if(UpUI == NULL) + { + UpUI = new UpdaterUI(); + UpUI->Event = &event; + UpUI->WindowState = &WindowState; + UpUI->renderer = renderer; + UpUI->Width = &Width; + UpUI->Height = &Height; + UpUI->IsDone = &done; + } + UpUI->DrawUI(); + } + break; } //If exit option was selected we need to set done to 1 - if(WindowState == 2) done = 1; + if(WindowState == 3) done = 1; //Draw the frame SDL_RenderPresent(renderer); } + plExit(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); - + return 0; }