diff --git a/HBC/META.XML b/HBC/META.XML index 7f9cc0aa..d927f42b 100644 --- a/HBC/META.XML +++ b/HBC/META.XML @@ -2,8 +2,8 @@ USB Loader GX USB Loader GX Team - 1.0 r837 - 200910150000 + 1.0 r839 + 200911212355 Loads games from USB-devices USB Loader GX is a libwiigui based USB iso loader with a wii-like GUI. You can install games to your HDDs and boot them with shorter loading times. The interactive GUI is completely controllable with WiiMote, Classic Controller or GC Controller. diff --git a/source/filelist.h b/source/filelist.h index 78970130..4e2716bd 100644 --- a/source/filelist.h +++ b/source/filelist.h @@ -537,4 +537,7 @@ extern const u32 usbport_png_size; extern const u8 dvd_png[]; extern const u32 dvd_png_size; +extern const u8 new_png[]; +extern const u32 new_png_size; + #endif diff --git a/source/images/new.png b/source/images/new.png new file mode 100644 index 00000000..167cce95 Binary files /dev/null and b/source/images/new.png differ diff --git a/source/libwiigui/gui_gamebrowser.cpp b/source/libwiigui/gui_gamebrowser.cpp index 36d4d1e3..dfeb4ba3 100644 --- a/source/libwiigui/gui_gamebrowser.cpp +++ b/source/libwiigui/gui_gamebrowser.cpp @@ -45,10 +45,13 @@ GuiGameBrowser::GuiGameBrowser(int w, int h, struct discHdr * l, int gameCnt, co snprintf(imgPath, sizeof(imgPath), "%sbg_options.png", themePath); bgGames = new GuiImageData(imgPath, imagebg); + snprintf(imgPath, sizeof(imgPath), "%snew.png", themePath); + newGames = new GuiImageData(imgPath, new_png); + bgGameImg = new GuiImage(bgGames); bgGameImg->SetParent(this); bgGameImg->SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE); - + maxTextWidth = bgGameImg->GetWidth() - 24 - 4; snprintf(imgPath, sizeof(imgPath), "%sbg_options_entry.png", themePath); @@ -121,6 +124,7 @@ GuiGameBrowser::GuiGameBrowser(int w, int h, struct discHdr * l, int gameCnt, co gameTxt = new GuiText * [pagesize]; gameTxtOver = new GuiText * [pagesize]; gameBg = new GuiImage * [pagesize]; + newImg = new GuiImage * [pagesize]; for(int i=0; i < pagesize; i++) { @@ -137,10 +141,15 @@ GuiGameBrowser::GuiGameBrowser(int w, int h, struct discHdr * l, int gameCnt, co gameBg[i] = new GuiImage(bgGamesEntry); + newImg[i] = new GuiImage(newGames); + newImg[i]->SetAlignment(ALIGN_RIGHT, ALIGN_MIDDLE); + newImg[i]->SetVisible(false); + game[i] = new GuiButton(width-28,GAMESELECTSIZE); game[i]->SetParent(this); game[i]->SetLabel(gameTxt[i]); game[i]->SetLabelOver(gameTxtOver[i]); + game[i]->SetIcon(newImg[i]); game[i]->SetImageOver(gameBg[i]); game[i]->SetPosition(5,GAMESELECTSIZE*i+4); game[i]->SetRumble(false); @@ -177,6 +186,7 @@ GuiGameBrowser::~GuiGameBrowser() delete bgGameImg; delete bgGames; delete bgGamesEntry; + delete newGames; delete trigA; delete trigHeldA; @@ -188,6 +198,7 @@ GuiGameBrowser::~GuiGameBrowser() delete gameTxtOver[i]; delete gameBg[i]; delete game[i]; + delete newImg[i]; } delete [] gameIndex; delete [] game; @@ -329,6 +340,17 @@ void GuiGameBrowser::UpdateListEntries() gameTxtOver[i]->SetText(get_title(&gameList[next])); gameTxtOver[i]->SetPosition(24, 0); + if (Settings.marknewtitles) { + if (gameList[next].isNew) { + gameTxt[i]->SetMaxWidth(maxTextWidth - (newGames->GetWidth() + 1), GuiText::DOTTED); + gameTxtOver[i]->SetMaxWidth(maxTextWidth - (newGames->GetWidth() + 1), GuiText::SCROLL); + } else { + gameTxt[i]->SetMaxWidth(maxTextWidth, GuiText::DOTTED); + gameTxtOver[i]->SetMaxWidth(maxTextWidth, GuiText::SCROLL); + } + newImg[i]->SetVisible(gameList[next].isNew != 0); + } + gameIndex[i] = next; next = this->FindMenuItem(next, 1); } diff --git a/source/libwiigui/gui_gamebrowser.h b/source/libwiigui/gui_gamebrowser.h index cc6a1a26..88c71df7 100644 --- a/source/libwiigui/gui_gamebrowser.h +++ b/source/libwiigui/gui_gamebrowser.h @@ -35,6 +35,7 @@ class GuiGameBrowser : public GuiElement GuiText ** gameTxt; GuiText ** gameTxtOver; GuiImage ** gameBg; + GuiImage ** newImg; GuiButton * arrowUpBtn; GuiButton * arrowDownBtn; @@ -51,6 +52,7 @@ class GuiGameBrowser : public GuiElement GuiImageData * bgGames; GuiImageData * bgGamesEntry; + GuiImageData * newGames; GuiImageData * scrollbar; GuiImageData * arrowDown; GuiImageData * arrowDownOver; diff --git a/source/menu.cpp b/source/menu.cpp index 847a8b6a..62d76f81 100644 --- a/source/menu.cpp +++ b/source/menu.cpp @@ -28,6 +28,7 @@ #include "menu.h" #include "sys.h" #include "wpad.h" +#include "settings/newtitles.h" /*** Variables that are also used extern ***/ GuiWindow * mainWindow = NULL; @@ -323,6 +324,7 @@ int MainMenu(int menu) { gprintf("\nExiting main GUI"); CloseXMLDatabase(); + NewTitles::DestroyInstance(); ExitGUIThreads(); bgMusic->Stop(); delete bgMusic; diff --git a/source/settings/Settings.cpp b/source/settings/Settings.cpp index ad1ecab6..514d3084 100644 --- a/source/settings/Settings.cpp +++ b/source/settings/Settings.cpp @@ -853,6 +853,14 @@ int MenuSettings() static const char *opts[settings_screensaver_max] = {trNOOP("OFF"),trNOOP("3 min"),trNOOP("5 min"),trNOOP("10 min"),trNOOP("20 min"),trNOOP("30 min"),trNOOP("1 hour")}; options2.SetValue(Idx,"%s",tr(opts[Settings.screensaver])); } + + if(ret == ++Idx || firstRun) + { + if(firstRun) options2.SetName(Idx, "%s",tr("Mark new games")); + if(ret == Idx && ++Settings.marknewtitles >= settings_off_on_max) + Settings.marknewtitles = 0; + options2.SetValue(Idx,"%s",tr(opts_off_on[Settings.marknewtitles])); + } firstRun = false; } diff --git a/source/settings/cfg.c b/source/settings/cfg.c index 939e0fc9..f0fbd911 100644 --- a/source/settings/cfg.c +++ b/source/settings/cfg.c @@ -355,6 +355,7 @@ void Global_Default(void) { Settings.db_JPtoEN = 0; Settings.screensaver = 3; Settings.partition = -1; + Settings.marknewtitles = 1; } @@ -1043,6 +1044,12 @@ void global_cfg_set(char *name, char *val) { Settings.partition = i; } return; + } else if (strcmp(name, "marknewtitles") == 0) { + int i; + if (sscanf(val, "%d", &i) == 1) { + Settings.marknewtitles = i; + } + return; } cfg_bool("godmode", &Settings.godmode); @@ -1287,6 +1294,7 @@ bool cfg_save_global() { // save global settings fprintf(f, "autonetwork = %d\n ", Settings.autonetwork); fprintf(f, "discart = %d\n ", Settings.discart); fprintf(f, "partition = %d\n", Settings.partition); + fprintf(f, "marknewtitles = %d\n", Settings.marknewtitles); fclose(f); return true; } diff --git a/source/settings/cfg.h b/source/settings/cfg.h index a84c0a20..0f858448 100644 --- a/source/settings/cfg.h +++ b/source/settings/cfg.h @@ -421,6 +421,7 @@ extern "C" { u8 autonetwork; u8 discart; short gamesound; + u8 marknewtitles; }; extern struct SSettings Settings; diff --git a/source/settings/newtitles.cpp b/source/settings/newtitles.cpp new file mode 100644 index 00000000..1663b6ac --- /dev/null +++ b/source/settings/newtitles.cpp @@ -0,0 +1,156 @@ +#include +#include + +#include "cfg.h" +#include "newtitles.h" + +#define NEW_SECONDS 24 * 60 * 60 +#define GAMETITLES "gametitles.txt" + +NewTitles *NewTitles::instance = NULL; + +NewTitles* NewTitles::Instance() +{ + if (instance == NULL) { + instance = new NewTitles(); + } + return instance; +} + +void NewTitles::DestroyInstance() +{ + if (instance != NULL) + { + delete instance; + instance = NULL; + } +} + +NewTitles::NewTitles() +{ + firstTitle = lastTitle = NULL; + isDirty = isNewFile = false; + + // Read the text file + char path[255]; + strcpy(path, Settings.titlestxt_path); + path[strlen(Settings.titlestxt_path) - 1] = '/'; + strcat(path, GAMETITLES); + + char line[20]; + FILE *fp = fopen(path, "r"); + if (fp != NULL) { + while (fgets(line, sizeof(line), fp)) { + // This is one line + if (line[0] != '#' || line[0] != ';') { + Title *title = new Title(); + if (sscanf(line, "%6c:%ld", (u8 *) &title->titleId, &title->timestamp) == 2) { + if (firstTitle == NULL) { + firstTitle = title; + lastTitle = title; + } else { + lastTitle->next = title; + lastTitle = title; + } + } + else { + delete title; // Invalid title entry, ignore + } + } + } + + fclose(fp); + } else { + isNewFile = true; + } +} + +NewTitles::~NewTitles() +{ + Save(); + + Title *t = firstTitle; + while (t != NULL) { + Title *temp = (Title *) t->next; + delete t; + t = temp; + } + firstTitle = lastTitle = NULL; +} + +bool NewTitles::IsNew(u8 *titleid) +{ + Title *t = firstTitle; + + while (t != NULL) { + // Loop all titles, search for the correct titleid + if (strcmp((const char *) titleid, (const char *) t->titleId) == 0) { + // This title is less than 24 hours old + if ((time(NULL) - t->timestamp) < NEW_SECONDS) { + // Only count the game as new when it's never been played through GX + return CFG_get_game_num(titleid)->count == 0; + } + return false; + } + t = (Title *) t->next; + } + + // Not found, add it + t = new Title(); + strncpy((char *) t->titleId, (char *) titleid, 6); + t->timestamp = time(NULL); + if (isNewFile) { + t->timestamp -= (NEW_SECONDS + 1); // Mark all games as not new if this is a new file + } + + if (firstTitle == NULL) { + firstTitle = t; + lastTitle = t; + } else { + lastTitle -> next = t; + lastTitle = t; + } + isDirty = true; + + return !isNewFile; // If this is a new file, return false +} + +void NewTitles::Remove(u8 *titleid) +{ + Title *t = firstTitle, *prev = NULL; + while (t != NULL) { + if (strcmp((const char *) titleid, (const char *) t->titleId) == 0) { + if (prev == NULL) { + firstTitle = (Title *) t->next; + } else { + prev->next = t->next; + } + delete t; + isDirty = true; + + return; + } + prev = t; + t = (Title *) t->next; + } +} + +void NewTitles::Save() +{ + if (!isDirty) return; + + char path[255]; + strcpy(path, Settings.titlestxt_path); + path[strlen(Settings.titlestxt_path) - 1] = '/'; + strcat(path, GAMETITLES); + + FILE *fp = fopen(path, "w"); + if (fp != NULL) { + Title *t = firstTitle; + while (t != NULL) { + fprintf(fp, "%s:%ld\n", t->titleId, t->timestamp); + t = (Title *) t->next; + } + fclose(fp); + } +} diff --git a/source/settings/newtitles.h b/source/settings/newtitles.h new file mode 100644 index 00000000..4bc46295 --- /dev/null +++ b/source/settings/newtitles.h @@ -0,0 +1,34 @@ +#ifndef _NEWTITLES_H +#define _NEWTITLES_H + +#include + +class NewTitles +{ +public: + static NewTitles *Instance(); + static void DestroyInstance(); + + void Save(); + bool IsNew(u8 *titleid); + void Remove(u8 *titleid); +private: + NewTitles(); + ~NewTitles(); + + static NewTitles *instance; + + class Title { + public: + u8 titleId[6]; + time_t timestamp; + void *next; + }; + + Title *firstTitle; + Title *lastTitle; + bool isDirty; + bool isNewFile; +}; + +#endif //_NEWTITLES_H diff --git a/source/usbloader/disc.h b/source/usbloader/disc.h index a06827e9..ea9ac179 100644 --- a/source/usbloader/disc.h +++ b/source/usbloader/disc.h @@ -19,7 +19,8 @@ extern "C" { u8 bufsize; /* Padding */ - u8 unused1[14]; + u8 isNew; // Use space from the padding below + u8 unused1[13]; /* Magic word */ u32 magic; diff --git a/source/usbloader/getentries.cpp b/source/usbloader/getentries.cpp index db4fcc03..d520a9ce 100644 --- a/source/usbloader/getentries.cpp +++ b/source/usbloader/getentries.cpp @@ -5,6 +5,7 @@ #include "main.h" #include #include "getentries.h" +#include "settings/newtitles.h" #include "../prompts/TitleBrowser.h" @@ -162,6 +163,9 @@ int __Menu_GetPrevFilter(int t, wchar_t* gameFilter, u32 gameFiltered, wchar_t * { struct discHdr *header = &buffer[i]; + /* Is new? */ + header->isNew = NewTitles::Instance()->IsNew(header->id); + /* Filter Favorite */ if (Settings.fave && t==0) { @@ -177,6 +181,8 @@ int __Menu_GetPrevFilter(int t, wchar_t* gameFilter, u32 gameFiltered, wchar_t * wchar_t *wname = FreeTypeGX::charToWideChar(get_title(header)); if(wname) nameList.push_back(wname); } + + NewTitles::Instance()->Save(); /* delete buffer */ if(buffer) free(buffer); @@ -473,6 +479,9 @@ int __Menu_GetGameList(int t, wchar_t* gameFilter, discHdr ** PgameList, u32 *Pg for (u32 i = 0; i < cnt; i++) { struct discHdr *header = &buffer[i]; + /* Is new? */ + header->isNew = NewTitles::Instance()->IsNew(header->id); + /* Filters */ if (Settings.fave && t==0) { struct Game_NUM* game_num = CFG_get_game_num(header->id); @@ -497,6 +506,8 @@ int __Menu_GetGameList(int t, wchar_t* gameFilter, discHdr ** PgameList, u32 *Pg buffer[cnt2] = buffer[i]; cnt2++; } + NewTitles::Instance()->Save(); + if(cnt > cnt2) { cnt = cnt2; diff --git a/svnrev.sh b/svnrev.sh index 5677dc6a..c798528e 100644 --- a/svnrev.sh +++ b/svnrev.sh @@ -34,6 +34,9 @@ EOF echo "svnrev.c created" >&2 fi echo >&2 + + rev_new=`expr $rev_new + 1` + rev_date=`date +%Y%m%d%k%M` cat < ./HBC/META.XML @@ -41,7 +44,7 @@ EOF USB Loader GX USB Loader GX Team 1.0 r$rev_new - 200910150000 + $rev_date Loads games from USB-devices USB Loader GX is a libwiigui based USB iso loader with a wii-like GUI. You can install games to your HDDs and boot them with shorter loading times. The interactive GUI is completely controllable with WiiMote, Classic Controller or GC Controller.