diff --git a/include/appsupport.h b/include/appsupport.h index 2793ad309..3c05aabb9 100644 --- a/include/appsupport.h +++ b/include/appsupport.h @@ -5,6 +5,23 @@ #define APP_MODE_UPDATE_DELAY 240 +#define APP_TITLE_MAX 128 +#define APP_PATH_MAX 128 +#define APP_BOOT_MAX 64 + +#define APP_CONFIG_TITLE "title" +#define APP_CONFIG_BOOT "boot" + +#define APP_TITLE_CONFIG_FILE "title.cfg" + +typedef struct +{ + char title[APP_TITLE_MAX + 1]; + char path[APP_PATH_MAX + 1]; + char boot[APP_BOOT_MAX + 1]; + u8 legacy; +} app_info_t; + void appInit(); item_list_t *appGetObject(int initOnly); diff --git a/include/config.h b/include/config.h index a3cda92f2..f6c36a551 100644 --- a/include/config.h +++ b/include/config.h @@ -67,6 +67,7 @@ enum CONFIG_INDEX { #define CONFIG_OPL_OVERSCAN "overscan" #define CONFIG_OPL_DISABLE_DEBUG "disable_debug" #define CONFIG_OPL_PS2LOGO "ps2logo" +#define CONFIG_OPL_GAME_LIST_CACHE "game_list_cache" #define CONFIG_OPL_EXIT_PATH "exit_path" #define CONFIG_OPL_AUTO_SORT "autosort" #define CONFIG_OPL_AUTO_REFRESH "autorefresh" diff --git a/include/dialogs.h b/include/dialogs.h index 12dd74c6e..5589ab36e 100644 --- a/include/dialogs.h +++ b/include/dialogs.h @@ -26,6 +26,7 @@ enum UI_ITEMS { CFG_DEBUG, CFG_PS2LOGO, + CFG_GAMELISTCACHE, CFG_EXITTO, CFG_DEFDEVICE, CFG_USBMODE, diff --git a/include/hddsupport.h b/include/hddsupport.h index 3f3b9b96e..01b7c3df9 100644 --- a/include/hddsupport.h +++ b/include/hddsupport.h @@ -20,8 +20,8 @@ typedef struct u8 ops2l_compat_flags; u8 dma_type; u8 dma_mode; + u8 disctype; u32 layer_break; - int disctype; u32 start_sector; u32 total_size_in_kb; } hdl_game_info_t; diff --git a/include/iosupport.h b/include/iosupport.h index fe7ba41fb..d8195682d 100644 --- a/include/iosupport.h +++ b/include/iosupport.h @@ -79,6 +79,9 @@ typedef struct { short int mode; + /// Device priority when it comes to locating art assets for apps. Higher value = lower priority. (< 0) means no support for art assets. + char appsPriority; + char enabled; unsigned char flags; @@ -96,6 +99,9 @@ typedef struct /// item description in localised form (used if value is not negative) int textId; + /// Path to applications storage on the device (set to NULL if not applicable). + char *appsPath; + void (*itemInit)(void); /** @return 1 if update is needed, 0 otherwise */ diff --git a/include/lang.h b/include/lang.h index e7cc27edd..4d8d7b5ad 100644 --- a/include/lang.h +++ b/include/lang.h @@ -266,6 +266,7 @@ enum _STR_IDS { _STR_SFX_VOLUME, _STR_BOOT_SND_VOLUME, _STR_CFM_VMODE_CHG, + _STR_CACHE_GAME_LIST, LANG_STR_COUNT }; diff --git a/include/opl.h b/include/opl.h index 12b24c88d..233a8972e 100644 --- a/include/opl.h +++ b/include/opl.h @@ -39,6 +39,12 @@ #define OPL_IS_DEV_BUILD 1 //Define if this build is a development build. +#ifdef OPL_IS_DEV_BUILD +#define OPL_FOLDER "CFG-DEV" +#else +#define OPL_FOLDER "CFG" +#endif + //Master password for disabling the parental lock. #define OPL_PARENTAL_LOCK_MASTER_PASS "989765" @@ -57,6 +63,11 @@ #define OPL_VMODE_CHANGE_CONFIRMATION_TIMEOUT_MS 10000 +int oplPath2Mode(const char *path); +char *oplGetModeText(int mode); +int oplGetAppImage(char *folder, int isRelative, char *value, char *suffix, GSTEXTURE *resultTex, short psm); +int oplScanApps(int (*callback)(const char *path, config_set_t *appConfig, void *arg), void *arg); + void setErrorMessage(int strId); void setErrorMessageWithCode(int strId, int error); int loadConfig(int types); @@ -115,6 +126,7 @@ int gXOff; int gYOff; int gOverscan; int gSelectButton; +int gGameListCache; int gEnableSFX; int gEnableBootSND; diff --git a/include/supportbase.h b/include/supportbase.h index f64413cde..d5442de7a 100644 --- a/include/supportbase.h +++ b/include/supportbase.h @@ -3,8 +3,11 @@ #define UL_GAME_NAME_MAX 32 #define ISO_GAME_NAME_MAX 64 +#define ISO_GAME_EXTENSION_MAX 4 #define GAME_STARTUP_MAX 12 +#define ISO_GAME_FNAME_MAX (ISO_GAME_NAME_MAX+ISO_GAME_EXTENSION_MAX) + enum GAME_FORMAT { GAME_FORMAT_USBLD = 0, GAME_FORMAT_OLD_ISO, @@ -15,11 +18,11 @@ typedef struct { char name[ISO_GAME_NAME_MAX + 1]; // MUST be the higher value from UL / ISO char startup[GAME_STARTUP_MAX + 1]; - char extension[5]; + char extension[ISO_GAME_EXTENSION_MAX + 1]; u8 parts; u8 media; u8 format; - int sizeMB; + u32 sizeMB; } base_game_info_t; typedef struct diff --git a/include/util.h b/include/util.h index 322c8ddb2..55023d759 100644 --- a/include/util.h +++ b/include/util.h @@ -44,6 +44,7 @@ enum CONSOLE_REGIONS { int InitConsoleRegionData(void); const char *GetSystemDataPath(void); char GetSystemFolderLetter(void); +int sysDeleteFolder(const char *folder); int CheckPS2Logo(int fd, u32 lba); diff --git a/src/appsupport.c b/src/appsupport.c index 243078d25..e36b21a87 100644 --- a/src/appsupport.c +++ b/src/appsupport.c @@ -5,6 +5,7 @@ #include "include/themes.h" #include "include/system.h" #include "include/ioman.h" +#include "include/util.h" #include "include/usbsupport.h" #include "include/ethsupport.h" @@ -14,10 +15,18 @@ static int appForceUpdate = 1; static int appItemCount = 0; static config_set_t *configApps; +static app_info_t *appsList; + +struct app_info_linked { + struct app_info_linked *next; + app_info_t app; +}; // forward declaration static item_list_t appItemList; +static void appFreeList(void); + static struct config_value_t *appGetConfigValue(int id) { struct config_value_t *cur = configApps->head; @@ -48,6 +57,7 @@ void appInit(void) appForceUpdate = 1; configGetInt(configGetByType(CONFIG_OPL), "app_frames_delay", &appItemList.delay); configApps = configGetByType(CONFIG_APPS); + appsList = NULL; appItemList.enabled = 1; } @@ -63,22 +73,171 @@ static int appNeedsUpdate(void) return 1; } -static int appUpdateItemList(void) +static int addAppsLegacyList(struct app_info_linked **appsLinkedList) { - appItemCount = 0; + struct config_value_t *cur; + struct app_info_linked *app; + int count; + configClear(configApps); configRead(configApps); - if (configApps->head) { - struct config_value_t *cur = configApps->head; - while (cur) { - cur = cur->next; - appItemCount++; + count = 0; + cur = configApps->head; + while (cur != NULL) + { + if (*appsLinkedList == NULL) + { + *appsLinkedList = malloc(sizeof(struct app_info_linked)); + app = *appsLinkedList; + app->next = NULL; + } + else + { + app = malloc(sizeof(struct app_info_linked)); + if (app != NULL) { + app->next = *appsLinkedList; + *appsLinkedList = app; + } + } + + if (app == NULL) + { + LOG("APPSUPPORT unable to allocate memory.\n"); + break; + } + + strncpy(app->app.title, cur->key, APP_TITLE_MAX + 1); + app->app.title[APP_TITLE_MAX] = '\0'; + + //Split the boot filename from the path. + const char *elfname = appGetELFName(cur->val); + if (elfname != cur->val) { + strncpy(app->app.boot, elfname, APP_BOOT_MAX + 1); + app->app.boot[APP_BOOT_MAX] = '\0'; + + int pathlen = (int)(elfname - cur->val) - 1; + if (cur->val[pathlen] == ':') //Discard only '/'. + pathlen++; + if (pathlen > APP_PATH_MAX) + pathlen = APP_PATH_MAX; + strncpy(app->app.path, cur->val, pathlen); + app->app.path[pathlen] = '\0'; + } else { + //Cannot split boot filename from the path, somehow. + strncpy(app->app.boot, cur->val, APP_BOOT_MAX + 1); + app->app.boot[APP_BOOT_MAX] = '\0'; + strncpy(app->app.path, cur->val, APP_PATH_MAX + 1); + app->app.path[APP_BOOT_MAX] = '\0'; + } + + app->app.legacy = 1; + count++; + cur = cur->next; + } + + return count; +} + +static int appScanCallback(const char *path, config_set_t *appConfig, void *arg) +{ + struct app_info_linked **appsLinkedList = (struct app_info_linked **)arg; + struct app_info_linked *app; + const char *title, *boot; + + if (configGetStr(appConfig, APP_CONFIG_TITLE, &title) != 0 + && configGetStr(appConfig, APP_CONFIG_BOOT, &boot) != 0) + { + if (*appsLinkedList == NULL) + { + *appsLinkedList = malloc(sizeof(struct app_info_linked)); + app = *appsLinkedList; + app->next = NULL; + } + else + { + app = malloc(sizeof(struct app_info_linked)); + if (app != NULL) + { + app->next = *appsLinkedList; + *appsLinkedList = app; + } + } + + if (app == NULL) + { + LOG("APPSUPPORT unable to allocate memory.\n"); + return -1; + } + + strncpy(app->app.title, title, APP_TITLE_MAX+1); + app->app.title[APP_TITLE_MAX] = '\0'; + strncpy(app->app.boot, boot, APP_BOOT_MAX+1); + app->app.boot[APP_BOOT_MAX] = '\0'; + strncpy(app->app.path, path, APP_PATH_MAX+1); + app->app.path[APP_PATH_MAX] = '\0'; + app->app.legacy = 0; + return 0; + } else { + LOG("APPSUPPORT item has no boot/title.\n"); + return 1; + } + + return -1; +} + +static int appUpdateItemList(void) +{ + struct app_info_linked *appsLinkedList, *appNext; + int i; + + appFreeList(); + + appsLinkedList = NULL; + + //Get legacy apps list first, so it is possible to use appGetConfigValue(id). + appItemCount += addAppsLegacyList(&appsLinkedList); + + //Scan devices for apps. + appItemCount += oplScanApps(&appScanCallback, &appsLinkedList); + + // Generate apps list + if (appItemCount > 0) + { + appsList = malloc(appItemCount * sizeof(app_info_t)); + + if (appsList != NULL) + { + for (i = 0; appsLinkedList != NULL; i++) + { //appsLinkedList contains items in reverse order. + memcpy(&appsList[appItemCount - i - 1], &appsLinkedList->app, sizeof(app_info_t)); + + appNext = appsLinkedList->next; + free(appsLinkedList); + appsLinkedList = appNext; + } + } + else + { + LOG("APPSUPPORT unable to allocate memory.\n"); + appItemCount = 0; } } + + LOG("APPSUPPORT %d apps loaded\n", appItemCount); + return appItemCount; } +static void appFreeList(void) +{ + if (appsList != NULL) + { + appsList = NULL; + appItemCount = 0; + } +} + static int appGetItemCount(void) { return appItemCount; @@ -86,60 +245,100 @@ static int appGetItemCount(void) static char *appGetItemName(int id) { - struct config_value_t *cur = appGetConfigValue(id); - return cur->key; + return appsList[id].title; } static int appGetItemNameLength(int id) { - return 32; + return CONFIG_KEY_NAME_LEN; } static char *appGetItemStartup(int id) { - struct config_value_t *cur = appGetConfigValue(id); - return appGetELFName(cur->val); + if (appsList[id].legacy) + { + struct config_value_t *cur = appGetConfigValue(id); + return cur->val; + } else { + int mode; + + mode = oplPath2Mode(appsList[id].path); + if (mode < 0) { + LOG("APPSUPPORT: cannot find mode for path: %s\n", filename); + return ""; + } + + return oplGetModeText(mode); + } } static void appDeleteItem(int id) { - struct config_value_t *cur = appGetConfigValue(id); - fileXioRemove(cur->val); - cur->key[0] = '\0'; - configApps->modified = 1; - configWrite(configApps); + if (appsList[id].legacy) + { + struct config_value_t *cur = appGetConfigValue(id); + fileXioRemove(cur->val); + cur->key[0] = '\0'; + configApps->modified = 1; + configWrite(configApps); + } else { + sysDeleteFolder(appsList[id].path); + } appForceUpdate = 1; } static void appRenameItem(int id, char *newName) { - struct config_value_t *cur = appGetConfigValue(id); - char value[256]; - strncpy(value, cur->val, sizeof(value)); - configRemoveKey(configApps, cur->key); - configSetStr(configApps, newName, value); - configWrite(configApps); + + if (appsList[id].legacy) + { + struct config_value_t *cur = appGetConfigValue(id); + + strncpy(value, cur->val, sizeof(value)); + configRemoveKey(configApps, cur->key); + configSetStr(configApps, newName, value); + configWrite(configApps); + } else { + config_set_t *appConfig; + + snprintf(value, sizeof(value), "%s/%s", appsList[id].path, APP_TITLE_CONFIG_FILE); + + appConfig = configAlloc(0, NULL, value); + if (appConfig != NULL) + { + configRead(appConfig); + configSetStr(appConfig, APP_CONFIG_TITLE, newName); + configWrite(appConfig); + + configFree(appConfig); + } + } appForceUpdate = 1; } static void appLaunchItem(int id, config_set_t *configSet) { - struct config_value_t *cur = appGetConfigValue(id); - int fd = fileXioOpen(cur->val, O_RDONLY, 0666); + int mode, fd; + const char *filename; + + //Retrieve configuration set by appGetConfig() + configGetStr(configSet, CONFIG_ITEM_STARTUP, &filename); + + fd = fileXioOpen(filename, O_RDONLY); if (fd >= 0) { fileXioClose(fd); - int exception = NO_EXCEPTION; - if (strncmp(cur->val, "pfs0:", 5) == 0) - exception = UNMOUNT_EXCEPTION; + //To keep the necessary device accessible, we will assume the mode that owns the device which contains the file to boot. + mode = oplPath2Mode(filename); + if (mode < 0) { + mode = APP_MODE; + LOG("APPSUPPORT warning: cannot find mode for path: %s\n", filename); + } - char filename[256]; - strncpy(filename, cur->val, sizeof(filename) - 1); - filename[sizeof(filename) - 1] = '\0'; - deinit(exception, APP_MODE); // CAREFUL: deinit will call appCleanUp, so configApps/cur will be freed + deinit(UNMOUNT_EXCEPTION, mode); // CAREFUL: deinit will call appCleanUp, so configApps/cur will be freed sysExecElf(filename); } else guiMsgBox(_l(_STR_ERR_FILE_INVALID), 0, NULL); @@ -148,32 +347,24 @@ static void appLaunchItem(int id, config_set_t *configSet) static config_set_t *appGetConfig(int id) { config_set_t *config = configAlloc(0, NULL, NULL); - struct config_value_t *cur = appGetConfigValue(id); - configSetStr(config, CONFIG_ITEM_NAME, appGetELFName(cur->val)); - configSetStr(config, CONFIG_ITEM_LONGNAME, cur->key); - configSetStr(config, CONFIG_ITEM_STARTUP, cur->val); + if (appsList[id].legacy) { + struct config_value_t *cur = appGetConfigValue(id); + configSetStr(config, CONFIG_ITEM_NAME, appGetELFName(cur->val)); + configSetStr(config, CONFIG_ITEM_LONGNAME, cur->key); + configSetStr(config, CONFIG_ITEM_STARTUP, cur->val); + } else { + char path[256]; + configSetStr(config, CONFIG_ITEM_NAME, appsList[id].boot); + configSetStr(config, CONFIG_ITEM_LONGNAME, appsList[id].title); + snprintf(path, sizeof(path), "%s/%s", appsList[id].path, appsList[id].boot); + configSetStr(config, CONFIG_ITEM_STARTUP, path); + } return config; } static int appGetImage(char *folder, int isRelative, char *value, char *suffix, GSTEXTURE *resultTex, short psm) { - value = appGetELFName(value); - // We search on ever devices from fatest to slowest (HDD > ETH > USB) - static item_list_t *listSupport = NULL; - if ((listSupport = hddGetObject(1))) { - if (listSupport->itemGetImage(folder, isRelative, value, suffix, resultTex, psm) >= 0) - return 0; - } - - if ((listSupport = ethGetObject(1))) { - if (listSupport->itemGetImage(folder, isRelative, value, suffix, resultTex, psm) >= 0) - return 0; - } - - if ((listSupport = usbGetObject(1))) - return listSupport->itemGetImage(folder, isRelative, value, suffix, resultTex, psm); - - return -1; + return oplGetAppImage(folder, isRelative, appGetELFName(value), suffix, resultTex, psm); } //This may be called, even if appInit() was not. @@ -181,6 +372,8 @@ static void appCleanUp(int exception) { if (appItemList.enabled) { LOG("APPSUPPORT CleanUp\n"); + + appFreeList(); } } @@ -189,11 +382,13 @@ static void appShutdown(void) { if (appItemList.enabled) { LOG("APPSUPPORT Shutdown\n"); + + appFreeList(); } } static item_list_t appItemList = { - APP_MODE, 0, MODE_FLAG_NO_COMPAT | MODE_FLAG_NO_UPDATE, MENU_MIN_INACTIVE_FRAMES, APP_MODE_UPDATE_DELAY, "Applications", _STR_APPS, &appInit, &appNeedsUpdate, &appUpdateItemList, + APP_MODE, -1, 0, MODE_FLAG_NO_COMPAT | MODE_FLAG_NO_UPDATE, MENU_MIN_INACTIVE_FRAMES, APP_MODE_UPDATE_DELAY, "Applications", _STR_APPS, NULL, &appInit, &appNeedsUpdate, &appUpdateItemList, &appGetItemCount, NULL, &appGetItemName, &appGetItemNameLength, &appGetItemStartup, &appDeleteItem, &appRenameItem, &appLaunchItem, &appGetConfig, &appGetImage, &appCleanUp, &appShutdown, NULL, APP_ICON }; diff --git a/src/dialogs.c b/src/dialogs.c index 8e1d21d3f..6571bb7f2 100644 --- a/src/dialogs.c +++ b/src/dialogs.c @@ -246,6 +246,11 @@ struct UIItem diaConfig[] = { {UI_BOOL, CFG_PS2LOGO, 1, 1, _STR_HINT_PS2LOGO, 0, 0, {.intvalue = {0, 0}}}, {UI_BREAK}, + {UI_LABEL, 0, 1, 1, -1, -40, 0, {.label = {NULL, _STR_CACHE_GAME_LIST}}}, + {UI_SPACER}, + {UI_BOOL, CFG_GAMELISTCACHE, 1, 1, -1, 0, 0, {.intvalue = {0, 0}}}, + {UI_BREAK}, + {UI_LABEL, 0, 1, 1, -1, -40, 0, {.label = {NULL, _STR_EXITTO}}}, {UI_SPACER}, {UI_STRING, CFG_EXITTO, 1, 1, _STR_HINT_EXITPATH, 0, 0, {.stringvalue = {"", "", NULL}}}, diff --git a/src/ethsupport.c b/src/ethsupport.c index 716b3d236..ad0576293 100644 --- a/src/ethsupport.c +++ b/src/ethsupport.c @@ -761,7 +761,7 @@ static int ethCheckVMC(char *name, int createSize) } static item_list_t ethGameList = { - ETH_MODE, 0, 0, MENU_MIN_INACTIVE_FRAMES, ETH_MODE_UPDATE_DELAY, "ETH Games", _STR_NET_GAMES, ðInit, ðNeedsUpdate, + ETH_MODE, 1, 0, 0, MENU_MIN_INACTIVE_FRAMES, ETH_MODE_UPDATE_DELAY, "ETH Games", _STR_NET_GAMES, "smb0:/APPS", ðInit, ðNeedsUpdate, ðUpdateGameList, ðGetGameCount, ðGetGame, ðGetGameName, ðGetGameNameLength, ðGetGameStartup, ðDeleteGame, ðRenameGame, ðLaunchGame, ðGetConfig, ðGetImage, ðCleanUp, ðShutdown, ðCheckVMC, ETH_ICON }; diff --git a/src/gui.c b/src/gui.c index 6f66bc80e..db155593a 100644 --- a/src/gui.c +++ b/src/gui.c @@ -383,6 +383,7 @@ void guiShowConfig() diaSetInt(diaConfig, CFG_DEBUG, gDisableDebug); diaSetInt(diaConfig, CFG_PS2LOGO, gPS2Logo); + diaSetInt(diaConfig, CFG_GAMELISTCACHE, gGameListCache); diaSetString(diaConfig, CFG_EXITTO, gExitPath); diaSetInt(diaConfig, CFG_ENWRITEOP, gEnableWrite); diaSetInt(diaConfig, CFG_HDDSPINDOWN, gHDDSpindown); @@ -405,6 +406,7 @@ void guiShowConfig() if (ret) { diaGetInt(diaConfig, CFG_DEBUG, &gDisableDebug); diaGetInt(diaConfig, CFG_PS2LOGO, &gPS2Logo); + diaGetInt(diaConfig, CFG_GAMELISTCACHE, &gGameListCache); diaGetString(diaConfig, CFG_EXITTO, gExitPath, sizeof(gExitPath)); diaGetInt(diaConfig, CFG_ENWRITEOP, &gEnableWrite); diaGetInt(diaConfig, CFG_HDDSPINDOWN, &gHDDSpindown); diff --git a/src/hdd.c b/src/hdd.c index a0cac3d3e..753e70817 100644 --- a/src/hdd.c +++ b/src/hdd.c @@ -155,7 +155,7 @@ static int hddGetHDLGameInfo(struct GameDataEntry *game, hdl_game_info_t *ginfo) ginfo->dma_type = hdl_header->dma_type; ginfo->dma_mode = hdl_header->dma_mode; ginfo->layer_break = hdl_header->layer1_start; - ginfo->disctype = hdl_header->discType; + ginfo->disctype = (u8)hdl_header->discType; ginfo->start_sector = game->lba; ginfo->total_size_in_kb = game->size * 2; //size * 2048 / 1024 = 2x } else diff --git a/src/hddsupport.c b/src/hddsupport.c index 6831808ff..613759fbe 100644 --- a/src/hddsupport.c +++ b/src/hddsupport.c @@ -17,7 +17,7 @@ #define OPL_HDD_MODE_PS2LOGO_OFFSET 0x17F8 -static unsigned char hddForceUpdate = 1; +static unsigned char hddForceUpdate = 0; static unsigned char hddHDProKitDetected = 0; static unsigned char hddModulesLoaded = 0; @@ -29,6 +29,9 @@ const char *oplPart = "hdd0:+OPL"; // forward declaration static item_list_t hddGameList; +static int hddLoadGameListCache(hdl_games_list_t *cache); +static int hddUpdateGameListCache(hdl_games_list_t *cache, hdl_games_list_t *game_list); + static void hddInitModules(void) { @@ -171,7 +174,7 @@ void hddLoadModules(void) void hddInit(void) { LOG("HDDSUPPORT Init\n"); - hddForceUpdate = 1; + hddForceUpdate = 0; //Use cache at initial startup. configGetInt(configGetByType(CONFIG_OPL), "hdd_frames_delay", &hddGameList.delay); ioPutRequest(IO_CUSTOM_SIMPLEACTION, &hddInitModules); hddGameList.enabled = 1; @@ -185,18 +188,32 @@ item_list_t *hddGetObject(int initOnly) } static int hddNeedsUpdate(void) -{ - if (hddForceUpdate) { - hddForceUpdate = 0; - return 1; - } - - return 0; +{ /* Auto refresh is disabled by setting HDD_MODE_UPDATE_DELAY to MENU_UPD_DELAY_NOUPDATE, within hddsupport.h. + Hence any update request would be issued by the user, which should be taken as an explicit request to re-scan the HDD. */ + return 1; } static int hddUpdateGameList(void) { - return (hddGetHDLGamelist(&hddGames) == 0 ? hddGames.count : 0); + hdl_games_list_t hddGamesNew; + int ret; + + if (((ret = hddLoadGameListCache(&hddGames)) != 0) || (hddForceUpdate)) + { + hddGamesNew.count = 0; + hddGamesNew.games = NULL; + ret = hddGetHDLGamelist(&hddGamesNew); + if (ret == 0) + { + hddUpdateGameListCache(&hddGames, &hddGamesNew); + hddFreeHDLGamelist(&hddGames); + hddGames = hddGamesNew; + } + } + + hddForceUpdate = 1; //Subsequent refresh operations will cause the HDD to be scanned. + + return (ret == 0 ? hddGames.count : 0); } static int hddGetGameCount(void) @@ -393,11 +410,7 @@ static config_set_t *hddGetConfig(int id) char path[256]; hdl_game_info_t *game = &hddGames.games[id]; -#ifdef OPL_IS_DEV_BUILD - snprintf(path, sizeof(path), "%sCFG-DEV/%s.cfg", hddPrefix, game->startup); -#else - snprintf(path, sizeof(path), "%sCFG/%s.cfg", hddPrefix, game->startup); -#endif + snprintf(path, sizeof(path), "%s"OPL_FOLDER"/%s.cfg", hddPrefix, game->startup); config_set_t *config = configAlloc(0, NULL, path); configRead(config); //Does not matter if the config file exists or not. @@ -471,8 +484,122 @@ static void hddShutdown(void) } } +static int hddLoadGameListCache(hdl_games_list_t *cache) +{ + char filename[256]; + FILE *file; + hdl_game_info_t *games; + int result, size, count; + + if (!gGameListCache) + return 1; + + hddFreeHDLGamelist(cache); + + sprintf(filename, "%s/games.bin", hddPrefix); + file = fopen(filename, "rb"); + if (file != NULL) + { + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + count = size / sizeof(hdl_game_info_t); + if (count > 0) + { + games = memalign(64, count * sizeof(hdl_game_info_t)); + if (games != NULL) + { + if (fread(games, sizeof(hdl_game_info_t), count, file) == count) + { + cache->count = count; + cache->games = games; + LOG("hddLoadGameListCache: %d games loaded.\n", count); + result = 0; + } else { + LOG("hddLoadGameListCache: I/O error.\n"); + free(games); + result = EIO; + } + } else { + LOG("hddLoadGameListCache: failed to allocate memory.\n"); + result = ENOMEM; + } + } else { + result = -1; //Empty file + } + + fclose(file); + } else { + result = ENOENT; + } + + return result; +} + +static int hddUpdateGameListCache(hdl_games_list_t *cache, hdl_games_list_t *game_list) +{ + char filename[256]; + FILE *file; + int result, i, j, modified; + + if (!gGameListCache) + return 1; + + if (cache->count > 0) + { + modified = 0; + for(i = 0; i < cache->count; i++) + { + for (j = 0; j < game_list->count; j++) + { + if (strncmp(cache->games[i].partition_name, game_list->games[j].partition_name, APA_IDMAX+1) == 0) + break; + } + + if (j == game_list->count) + { + LOG("hddUpdateGameListCache: game added.\n"); + modified = 1; + break; + } + } + + if ((!modified) && (game_list->count != cache->count)) + { + LOG("hddUpdateGameListCache: game removed.\n"); + modified = 1; + } + } else { + modified = (game_list->count > 0) ? 1 : 0; + } + + if (!modified) + return 0; + LOG("hddUpdateGameListCache: caching new game list.\n"); + + sprintf(filename, "%s/games.bin", hddPrefix); + if (game_list->count > 0) + { + file = fopen(filename, "wb"); + if (file != NULL) + { + result = (fwrite(game_list->games, sizeof(hdl_game_info_t), game_list->count, file) == game_list->count) ? 0 : EIO; + fclose(file); + } else { + result = EIO; + } + } else { + //Last game deleted. + remove(filename); + result = 0; + } + + return result; +} + static item_list_t hddGameList = { - HDD_MODE, 0, MODE_FLAG_COMPAT_DMA, MENU_MIN_INACTIVE_FRAMES, HDD_MODE_UPDATE_DELAY, "HDD Games", _STR_HDD_GAMES, &hddInit, &hddNeedsUpdate, &hddUpdateGameList, + HDD_MODE, 0, 0, MODE_FLAG_COMPAT_DMA, MENU_MIN_INACTIVE_FRAMES, HDD_MODE_UPDATE_DELAY, "HDD Games", _STR_HDD_GAMES, "pfs0:/APPS", &hddInit, &hddNeedsUpdate, &hddUpdateGameList, &hddGetGameCount, &hddGetGame, &hddGetGameName, &hddGetGameNameLength, &hddGetGameStartup, &hddDeleteGame, &hddRenameGame, &hddLaunchGame, &hddGetConfig, &hddGetImage, &hddCleanUp, &hddShutdown, &hddCheckVMC, HDD_ICON }; diff --git a/src/lang.c b/src/lang.c index 61e5d4d8b..627e5d2cf 100644 --- a/src/lang.c +++ b/src/lang.c @@ -274,6 +274,7 @@ static char *internalEnglish[LANG_STR_COUNT] = { "Sound Effects Volume", "Boot Sound Volume", "Confirm video mode change?", + "Cache Game List", }; static int guiLangID = 0; diff --git a/src/opl.c b/src/opl.c index a095b29cf..f7ea4b684 100644 --- a/src/opl.c +++ b/src/opl.c @@ -36,6 +36,10 @@ #include #include +#ifdef PADEMU +#include +#include +#endif #ifdef __EESIO_DEBUG #include @@ -349,6 +353,112 @@ static void deinitAllSupport(int exception, int modeSelected) moduleCleanup(&list_support[APP_MODE], exception, modeSelected); } +char *oplGetModeText(int mode) +{ + return(list_support[mode].support->textId == -1 ? list_support[mode].support->text : _l(list_support[mode].support->textId)); +} + +//For resolving the mode, given an app's path +int oplPath2Mode(const char *path) +{ + const char *blkdevnameend; + int i, blkdevnamelen; + item_list_t *listSupport; + + for (i = 0; i < MODE_COUNT; i++) + { + listSupport = list_support[i].support; + if ((listSupport != NULL) && (listSupport->appsPath != NULL)) + { + blkdevnameend = strchr(listSupport->appsPath, ':'); + if (blkdevnameend != NULL) + { + blkdevnamelen = (int)(blkdevnameend - listSupport->appsPath) + 1; + + if (strncmp(path, listSupport->appsPath, blkdevnamelen) == 0) + return listSupport->mode; + } + } + } + + return -1; +} + +int oplGetAppImage(char *folder, int isRelative, char *value, char *suffix, GSTEXTURE *resultTex, short psm) +{ + int i, remaining; + char priority; + item_list_t *listSupport; + + // We search on ever devices from fatest to slowest (HDD > ETH > USB) + for (remaining = MODE_COUNT,priority = 0; remaining > 0 && priority < 4; priority++) + { + for (i = 0; i < MODE_COUNT; i++) + { + listSupport = list_support[i].support; + + if (listSupport->appsPriority == priority) + { + if (listSupport->itemGetImage(folder, isRelative, value, suffix, resultTex, psm) >= 0) + return 0; + remaining--; + } + } + } + + return -1; +} + +int oplScanApps(int (*callback)(const char *path, config_set_t *appConfig, void *arg), void *arg) +{ + iox_dirent_t dirent; + int i, fd, count, ret; + item_list_t *listSupport; + config_set_t *appConfig; + char dir[128]; + char path[128]; + + count = 0; + for (i = 0; i < MODE_COUNT; i++) + { + listSupport = list_support[i].support; + if ((listSupport != NULL) && (listSupport->appsPath != NULL) && (listSupport->enabled)) + { + if ((fd = fileXioDopen(listSupport->appsPath)) > 0) + { + while (fileXioDread(fd, &dirent) > 0) + { + if (strcmp(dirent.name, ".") == 0 || strcmp(dirent.name, "..") == 0 || (!FIO_S_ISDIR(dirent.stat.mode))) + continue; + + snprintf(dir, sizeof(dir), "%s/%s", listSupport->appsPath, dirent.name); + snprintf(path, sizeof(path), "%s/%s", dir, APP_TITLE_CONFIG_FILE); + appConfig = configAlloc(0, NULL, path); + if (appConfig != NULL) + { + configRead(appConfig); + + ret = callback(dir, appConfig, arg); + configFree(appConfig); + + if (ret == 0) + count++; + else if (ret < 0) + { //Stopped because of unrecoverable error. + break; + } + } + } + + fileXioDclose(fd); + } else + LOG("APPS failed to open dir %s\n", listSupport->appsPath); + } + } + + return count; +} + // ---------------------------------------------------------- // ----------------------- Updaters ------------------------- // ---------------------------------------------------------- @@ -595,6 +705,7 @@ static void _loadConfig() configGetInt(configOPL, CONFIG_OPL_DISABLE_DEBUG, &gDisableDebug); configGetInt(configOPL, CONFIG_OPL_PS2LOGO, &gPS2Logo); + configGetInt(configOPL, CONFIG_OPL_GAME_LIST_CACHE, &gGameListCache); configGetStrCopy(configOPL, CONFIG_OPL_EXIT_PATH, gExitPath, sizeof(gExitPath)); configGetInt(configOPL, CONFIG_OPL_AUTO_SORT, &gAutosort); configGetInt(configOPL, CONFIG_OPL_AUTO_REFRESH, &gAutoRefresh); @@ -746,6 +857,7 @@ static void _saveConfig() configSetInt(configOPL, CONFIG_OPL_OVERSCAN, gOverscan); configSetInt(configOPL, CONFIG_OPL_DISABLE_DEBUG, gDisableDebug); configSetInt(configOPL, CONFIG_OPL_PS2LOGO, gPS2Logo); + configSetInt(configOPL, CONFIG_OPL_GAME_LIST_CACHE, gGameListCache); configSetStr(configOPL, CONFIG_OPL_EXIT_PATH, gExitPath); configSetInt(configOPL, CONFIG_OPL_AUTO_SORT, gAutosort); configSetInt(configOPL, CONFIG_OPL_AUTO_REFRESH, gAutoRefresh); @@ -1311,6 +1423,7 @@ static void setDefaults(void) gAutoRefresh = 0; gDisableDebug = 1; gPS2Logo = 0; + gGameListCache = 0; gEnableWrite = 0; gRememberLastPlayed = 0; gAutoStartLastPlayed = 9; diff --git a/src/supportbase.c b/src/supportbase.c index c11120aac..b8532dcc8 100644 --- a/src/supportbase.c +++ b/src/supportbase.c @@ -19,6 +19,11 @@ struct game_list_t struct game_list_t *next; }; +struct game_cache_list { + unsigned int count; + base_game_info_t *games; +}; + int sbIsSameSize(const char *prefix, int prevSize) { int size = -1; @@ -109,12 +114,193 @@ static inline int GetStartupExecName(const char *path, char *filename, int maxle return result; } +static void freeISOGameListCache(struct game_cache_list *cache); + +static int loadISOGameListCache(const char *path, struct game_cache_list *cache) +{ + char filename[256]; + FILE *file; + base_game_info_t *games; + int result, size, count; + + if (!gGameListCache) + return 1; + + freeISOGameListCache(cache); + + sprintf(filename, "%s/games.bin", path); + file = fopen(filename, "rb"); + if (file != NULL) + { + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + count = size / sizeof(base_game_info_t); + if (count > 0) + { + games = memalign(64, count * sizeof(base_game_info_t)); + if (games != NULL) + { + if (fread(games, sizeof(base_game_info_t), count, file) == count) + { + LOG("loadISOGameListCache: %d games loaded.\n", count); + cache->count = count; + cache->games = games; + result = 0; + } else { + LOG("loadISOGameListCache: I/O error.\n"); + free(games); + result = EIO; + } + } else { + LOG("loadISOGameListCache: failed to allocate memory.\n"); + result = ENOMEM; + } + } else { + result = -1; //Empty file (should not happen) + } + + fclose(file); + } else { + result = ENOENT; + } + + return result; +} + +static void freeISOGameListCache(struct game_cache_list *cache) +{ + if (cache->games != NULL) + { + free(cache->games); + cache->games = NULL; + cache->count = 0; + } +} + +static int updateISOGameList(const char *path, const struct game_cache_list *cache, const struct game_list_t *head, int count) +{ + char filename[256]; + FILE *file; + const struct game_list_t *game; + int result, i, j, modified; + base_game_info_t *list; + + if (!gGameListCache) + return 1; + + modified = 0; + if (cache != NULL) + { + if ((head != NULL) && (count > 0)) + { + game = head; + + for (i = 0; i < count; i++) + { + for (j = 0; j < cache->count; j++) + { + if (strncmp(cache->games[i].name, game->gameinfo.name, ISO_GAME_NAME_MAX+1) == 0 + && strncmp(cache->games[i].extension, game->gameinfo.extension, ISO_GAME_EXTENSION_MAX+1) == 0) + break; + } + + if (j == cache->count) + { + LOG("updateISOGameList: game added.\n"); + modified = 1; + break; + } + + game = game->next; + } + + if ((!modified) && (count != cache->count)) + { + LOG("updateISOGameList: game removed.\n"); + modified = 1; + } + } else { + modified = 0; + } + } else { + modified = ((head != NULL) && (count > 0)) ? 1 : 0; + } + + if (!modified) + return 0; + LOG("updateISOGameList: caching new game list.\n"); + + result = 0; + sprintf(filename, "%s/games.bin", path); + if ((head != NULL) && (count > 0)) + { + list = (base_game_info_t *)memalign(64, sizeof(base_game_info_t) * count); + + if (list != NULL) { + // Convert the linked list into a flat array, for writing performance. + game = head; + for (i = 0; (i < count) && (game != NULL); i++, game = game->next) { + // copy one game, advance + memcpy(&list[i], &game->gameinfo, sizeof(base_game_info_t)); + } + + file = fopen(filename, "wb"); + if (file != NULL) + { + result = fwrite(list, sizeof(base_game_info_t), count, file) == count ? 0 : EIO; + + fclose(file); + + if (result != 0) + remove(filename); + } else + result = EIO; + + free(list); + } else + result = ENOMEM; + } else { + //Last game deleted. + remove(filename); + } + + return result; +} + +//Queries for the game entry, based on filename. Only the new filename format is supported (filename.ext). +static int queryISOGameListCache(const struct game_cache_list *cache, base_game_info_t *ginfo, const char *filename) +{ + char isoname[ISO_GAME_FNAME_MAX+1]; + int i; + + for (i = 0; i < cache->count; i++) + { + snprintf(isoname, sizeof(isoname), "%s%s", cache->games[i].name, cache->games[i].extension); + + if (strcmp(filename, isoname) == 0) + { + memcpy(ginfo, &cache->games[i], sizeof(base_game_info_t)); + return 0; + } + } + + return ENOENT; +} + static int scanForISO(char *path, char type, struct game_list_t **glist) { - int fd, NameLen, count = 0, format, MountFD; + int fd, NameLen, count = 0, format, MountFD, cacheLoaded; + struct game_cache_list cache; + base_game_info_t cachedGInfo; char fullpath[256], startup[GAME_STARTUP_MAX]; iox_dirent_t record; + cache.games = NULL; + cache.count = 0; + cacheLoaded = loadISOGameListCache(path, &cache) == 0; + if ((fd = fileXioDopen(path)) > 0) { while (fileXioDread(fd, &record) > 0) { if ((format = isValidIsoName(record.name, &NameLen)) > 0) { @@ -143,37 +329,54 @@ static int scanForISO(char *path, char type, struct game_list_t **glist) break; } } else { - sprintf(fullpath, "%s/%s", path, record.name); - if ((MountFD = fileXioMount("iso:", fullpath, FIO_MT_RDONLY)) >= 0) { - if (GetStartupExecName("iso:/SYSTEM.CNF;1", startup, GAME_STARTUP_MAX - 1) == 0) { - struct game_list_t *next = (struct game_list_t *)malloc(sizeof(struct game_list_t)); - - if (next != NULL) { - next->next = *glist; - *glist = next; - - game = &(*glist)->gameinfo; - - strcpy(game->startup, startup); - strncpy(game->name, record.name, NameLen); - game->name[NameLen] = '\0'; - strncpy(game->extension, &record.name[NameLen], sizeof(game->extension)); - game->extension[sizeof(game->extension) - 1] = '\0'; + if(queryISOGameListCache(&cache, &cachedGInfo, record.name) != 0) { + sprintf(fullpath, "%s/%s", path, record.name); + + if ((MountFD = fileXioMount("iso:", fullpath, FIO_MT_RDONLY)) >= 0) { + if (GetStartupExecName("iso:/SYSTEM.CNF;1", startup, GAME_STARTUP_MAX - 1) == 0) { + struct game_list_t *next = (struct game_list_t *)malloc(sizeof(struct game_list_t)); + + if (next != NULL) { + next->next = *glist; + *glist = next; + + game = &(*glist)->gameinfo; + + strcpy(game->startup, startup); + strncpy(game->name, record.name, NameLen); + game->name[NameLen] = '\0'; + strncpy(game->extension, &record.name[NameLen], sizeof(game->extension)); + game->extension[sizeof(game->extension) - 1] = '\0'; + } else { + //Out of memory. + fileXioUmount("iso:"); + break; + } } else { - //Out of memory. + //Unable to parse SYSTEM.CNF. fileXioUmount("iso:"); - break; + continue; } - } else { - //Unable to parse SYSTEM.CNF. + fileXioUmount("iso:"); + } else { + //Unable to mount game. continue; } - - fileXioUmount("iso:"); } else { - //Unable to mount game. - continue; + //Entry was found in cache. + struct game_list_t *next = (struct game_list_t *)malloc(sizeof(struct game_list_t)); + + if (next != NULL) { + next->next = *glist; + *glist = next; + + game = &(*glist)->gameinfo; + memcpy(game, &cachedGInfo, sizeof(base_game_info_t)); + } else { + //Out of memory. + break; + } } } @@ -190,6 +393,14 @@ static int scanForISO(char *path, char type, struct game_list_t **glist) count = fd; } + if (cacheLoaded) + { + updateISOGameList(path, &cache, *glist, count); + freeISOGameListCache(&cache); + } else { + updateISOGameList(path, NULL, *glist, count); + } + return count; } @@ -523,11 +734,7 @@ void sbRename(base_game_info_t **list, const char *prefix, const char *sep, int config_set_t *sbPopulateConfig(base_game_info_t *game, const char *prefix, const char *sep) { char path[256]; -#if OPL_IS_DEV_BUILD - snprintf(path, sizeof(path), "%sCFG-DEV%s%s.cfg", prefix, sep, game->startup); -#else - snprintf(path, sizeof(path), "%sCFG%s%s.cfg", prefix, sep, game->startup); -#endif + snprintf(path, sizeof(path), "%s"OPL_FOLDER"%s%s.cfg", prefix, sep, game->startup); config_set_t *config = configAlloc(0, NULL, path); configRead(config); //Does not matter if the config file could be loaded or not. @@ -543,37 +750,26 @@ config_set_t *sbPopulateConfig(base_game_info_t *game, const char *prefix, const return config; } -void sbCreateFolders(const char *path, int createDiscImgFolders) +static void sbCreateFoldersFromList(const char *path, const char **folders) { - // update Themes + int i; char fullpath[256]; -#if OPL_IS_DEV_BUILD - sprintf(fullpath, "%sCFG-DEV", path); -#else - sprintf(fullpath, "%sCFG", path); -#endif - fileXioMkdir(fullpath, 0777); - - sprintf(fullpath, "%sTHM", path); - fileXioMkdir(fullpath, 0777); - - sprintf(fullpath, "%sART", path); - fileXioMkdir(fullpath, 0777); - - if (createDiscImgFolders) { - sprintf(fullpath, "%sCD", path); - fileXioMkdir(fullpath, 0777); - - sprintf(fullpath, "%sDVD", path); + for (i = 0; folders[i] != NULL; i++) { + sprintf(fullpath, "%s%s", path, folders[i]); fileXioMkdir(fullpath, 0777); } +} + +void sbCreateFolders(const char *path, int createDiscImgFolders) +{ + const char *basicFolders[] = { OPL_FOLDER, "THM", "ART", "VMC", "CHT", "APPS", NULL }; + const char *discImgFolders[] = { "CD", "DVD", NULL }; - sprintf(fullpath, "%sVMC", path); - fileXioMkdir(fullpath, 0777); + sbCreateFoldersFromList(path, basicFolders); - sprintf(fullpath, "%sCHT", path); - fileXioMkdir(fullpath, 0777); + if (createDiscImgFolders) + sbCreateFoldersFromList(path, discImgFolders); } int sbLoadCheats(const char *path, const char *file) diff --git a/src/system.c b/src/system.c index 0b29b7ae1..590980619 100644 --- a/src/system.c +++ b/src/system.c @@ -811,9 +811,7 @@ void sysLaunchLoaderElf(const char *filename, const char *mode_str, int size_cdv argv[i] = gsm_config_str; i++; - strcpy(ElfPath, "cdrom0:\\"); - strncat(ElfPath, filename, 11); // fix for 8+3 filename. - strcat(ElfPath, ";1"); + snprintf(ElfPath, sizeof(ElfPath), "cdrom0:\\%s;1", filename); // Let's go. fileXioExit(); diff --git a/src/usbsupport.c b/src/usbsupport.c index b8b089092..b577c1633 100644 --- a/src/usbsupport.c +++ b/src/usbsupport.c @@ -420,7 +420,7 @@ static int usbCheckVMC(char *name, int createSize) } static item_list_t usbGameList = { - USB_MODE, 0, 0, MENU_MIN_INACTIVE_FRAMES, USB_MODE_UPDATE_DELAY, "USB Games", _STR_USB_GAMES, &usbInit, &usbNeedsUpdate, + USB_MODE, 2, 0, 0, MENU_MIN_INACTIVE_FRAMES, USB_MODE_UPDATE_DELAY, "USB Games", _STR_USB_GAMES, "mass0:/APPS", &usbInit, &usbNeedsUpdate, &usbUpdateGameList, &usbGetGameCount, &usbGetGame, &usbGetGameName, &usbGetGameNameLength, &usbGetGameStartup, &usbDeleteGame, &usbRenameGame, &usbLaunchGame, &usbGetConfig, &usbGetImage, &usbCleanUp, &usbShutdown, &usbCheckVMC, USB_ICON }; diff --git a/src/util.c b/src/util.c index 4bbfa46ac..0bcaed17d 100644 --- a/src/util.c +++ b/src/util.c @@ -488,6 +488,86 @@ int CheckPS2Logo(int fd, u32 lba) return ValidPS2Logo; } +struct DirentToDelete { + struct DirentToDelete *next; + char *filename; +}; + +int sysDeleteFolder(const char *folder) +{ + int fd, result; + char *path; + iox_dirent_t dirent; + struct DirentToDelete *head, *start; + + result = 0; + start = head = NULL; + if((fd = fileXioDopen(folder)) >= 0) { + /* Generate a list of files in the directory. */ + while(fileXioDread(fd, &dirent) > 0) { + if((strcmp(dirent.name, ".") == 0) || ((strcmp(dirent.name, "..") == 0))) + continue; + + if(FIO_S_ISDIR(dirent.stat.mode)) { + if((path = malloc(strlen(folder)+strlen(dirent.name) + 2)) != NULL) { + sprintf(path, "%s/%s", folder, dirent.name); + result = sysDeleteFolder(path); + free(path); + } + } else { + if(start == NULL) { + head = malloc(sizeof(struct DirentToDelete)); + if(head == NULL) + break; + start = head; + } else { + if((head->next = malloc(sizeof(struct DirentToDelete))) == NULL) + break; + + head=head->next; + } + + head->next=NULL; + + if((head->filename = malloc(strlen(dirent.name) + 1)) != NULL) + strcpy(head->filename, dirent.name); + else + break; + } + } + + fileXioDclose(fd); + } else + result = fd; + + if (result >= 0) { + /* Delete the files. */ + for (head = start; head != NULL; head = start) { + if(head->filename != NULL) { + if((path = malloc(strlen(folder) + strlen(head->filename) + 2)) != NULL) { + sprintf(path, "%s/%s", folder, head->filename); + result=fileXioRemove(path); + if (result < 0) + LOG("sysDeleteFolder: failed to remove %s: %d\n", path, result); + + free(path); + } + free(head->filename); + } + + start = head->next; + free(head); + } + + if(result >= 0) { + result = fileXioRmdir(folder); + LOG("sysDeleteFolder: failed to rmdir %s: %d\n", folder, result); + } + } + + return result; +} + /*----------------------------------------------------------------------------------------*/ /* NOP delay. */ /*----------------------------------------------------------------------------------------*/