diff --git a/source/common/ui.c b/source/common/ui.c index 2aac7d7..f110add 100644 --- a/source/common/ui.c +++ b/source/common/ui.c @@ -46,6 +46,21 @@ void DrawRectangle(u8* screen, int x, int y, int width, int height, int color) } } +void DrawBitmap(u8* screen, int x, int y, int w, int h, u8* bitmap) +{ + u8* bitmapPos = bitmap; + for (int yy = 0; yy < h; yy++) { + int xDisplacement = (x * BYTES_PER_PIXEL * SCREEN_HEIGHT); + int yDisplacement = ((SCREEN_HEIGHT - (y + yy) - 1) * BYTES_PER_PIXEL); + u8* screenPos = screen + xDisplacement + yDisplacement; + for (int xx = w - 1; xx >= 0; xx--) { + memcpy(screenPos, bitmapPos, BYTES_PER_PIXEL); + bitmapPos += BYTES_PER_PIXEL; + screenPos += BYTES_PER_PIXEL * SCREEN_HEIGHT; + } + } +} + void DrawCharacter(u8* screen, int character, int x, int y, int color, int bgcolor) { for (int yy = 0; yy < FONT_HEIGHT; yy++) { @@ -110,6 +125,27 @@ u32 GetDrawStringWidth(const char* str) { return width; } +void WordWrapString(char* str, int llen) { + char* last_brk = str - 1; + char* last_spc = str - 1; + if (!llen) llen = (SCREEN_WIDTH_TOP / FONT_WIDTH); + for (char* str_ptr = str;; str_ptr++) { + if (!*str_ptr || (*str_ptr == ' ')) { // on space or string_end + if (str_ptr - last_brk > llen) { // if maximum line lenght is exceeded + if (last_spc > last_brk) { // put a line_brk at the last space + *last_spc = '\n'; + last_brk = last_spc; + last_spc = str_ptr; + } else if (*str_ptr) { // if we have no applicable space + *str_ptr = '\n'; + last_brk = str_ptr; + } + } else if (*str_ptr) last_spc = str_ptr; + } else if (*str_ptr == '\n') last_brk = str_ptr; + if (!*str_ptr) break; + } +} + void ResizeString(char* dest, const char* orig, int nsize, int tpos, bool align_right) { int osize = strnlen(orig, 256); if (nsize < osize) { @@ -180,6 +216,33 @@ void ShowString(const char *format, ...) } else ClearScreenF(true, false, COLOR_STD_BG); } +void ShowIconString(u8* icon, int w, int h, const char *format, ...) +{ + static const u32 icon_offset = 10; + u32 str_width, str_height, tot_height; + u32 x_str, y_str, x_bmp, y_bmp; + + ClearScreenF(true, false, COLOR_STD_BG); + if (!format || !*format) return; // only if there is something in there + + char str[STRBUF_SIZE] = { 0 }; + va_list va; + va_start(va, format); + vsnprintf(str, STRBUF_SIZE, format, va); + va_end(va); + + str_width = GetDrawStringWidth(str); + str_height = GetDrawStringHeight(str); + tot_height = h + icon_offset + str_height; + x_str = (str_width >= SCREEN_WIDTH_TOP) ? 0 : (SCREEN_WIDTH_TOP - str_width) / 2; + y_str = (str_height >= SCREEN_HEIGHT) ? 0 : h + icon_offset + (SCREEN_HEIGHT - tot_height) / 2; + x_bmp = (w >= SCREEN_WIDTH_TOP) ? 0 : (SCREEN_WIDTH_TOP - w) / 2; + y_bmp = (tot_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - tot_height) / 2; + + DrawBitmap(TOP_SCREEN, x_bmp, y_bmp, w, h, icon); + DrawStringF(TOP_SCREEN, x_str, y_str, COLOR_STD_FONT, COLOR_STD_BG, str); +} + bool ShowPrompt(bool ask, const char *format, ...) { u32 str_width, str_height; diff --git a/source/common/ui.h b/source/common/ui.h index 60b78e0..b4f5606 100644 --- a/source/common/ui.h +++ b/source/common/ui.h @@ -58,6 +58,7 @@ void ClearScreen(unsigned char *screen, int color); void ClearScreenF(bool clear_top, bool clear_bottom, int color); void DrawRectangle(u8* screen, int x, int y, int width, int height, int color); +void DrawBitmap(u8* screen, int x, int y, int w, int h, u8* bitmap); void DrawCharacter(unsigned char *screen, int character, int x, int y, int color, int bgcolor); void DrawString(unsigned char *screen, const char *str, int x, int y, int color, int bgcolor); @@ -66,12 +67,14 @@ void DrawStringF(unsigned char *screen, int x, int y, int color, int bgcolor, co u32 GetDrawStringHeight(const char* str); u32 GetDrawStringWidth(const char* str); +void WordWrapString(char* str, int llen); void ResizeString(char* dest, const char* orig, int nsize, int tpos, bool align_right); void TruncateString(char* dest, const char* orig, int nsize, int tpos); void FormatNumber(char* str, u64 number); void FormatBytes(char* str, u64 bytes); void ShowString(const char *format, ...); +void ShowIconString(u8* icon, int w, int h, const char *format, ...); bool ShowPrompt(bool ask, const char *format, ...); bool ShowUnlockSequence(u32 seqlvl, const char *format, ...); u32 ShowSelectPrompt(u32 n, const char** options, const char *format, ...); diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 657ec8a..72acffc 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -8,6 +8,7 @@ u32 IdentifyFileType(const char* path) { const u8 romfs_magic[] = { ROMFS_MAGIC }; const u8 tickdb_magic[] = { TICKDB_MAGIC }; + const u8 smdh_magic[] = { SMDH_MAGIC }; u8 header[0x200] __attribute__((aligned(32))); // minimum required size void* data = (void*) header; size_t fsize = FileGetSize(path); @@ -60,6 +61,8 @@ u32 IdentifyFileType(const char* path) { return SYS_FIRM; // FIRM file } else if (memcmp(header + 0x100, tickdb_magic, sizeof(tickdb_magic)) == 0) { return SYS_TICKDB; // ticket.db + } else if (memcmp(header, smdh_magic, sizeof(smdh_magic)) == 0) { + return GAME_SMDH; // SMDH file } } if ((fsize > sizeof(BossHeader)) && diff --git a/source/fs/filetype.h b/source/fs/filetype.h index 46b0ac6..3f4f627 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -13,11 +13,12 @@ #define GAME_BOSS (1UL<<8) #define GAME_NUSCDN (1UL<<9) #define GAME_TICKET (1UL<<10) -#define SYS_FIRM (1UL<<11) -#define SYS_TICKDB (1UL<<12) -#define BIN_NCCHNFO (1UL<<13) -#define BIN_LAUNCH (1UL<<14) -#define BIN_SUPPORT (1UL<<15) +#define GAME_SMDH (1UL<<11) +#define SYS_FIRM (1UL<<12) +#define SYS_TICKDB (1UL<<13) +#define BIN_NCCHNFO (1UL<<14) +#define BIN_LAUNCH (1UL<<15) +#define BIN_SUPPORT (1UL<<16) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define FLAG_CTR (1UL<<29) @@ -30,6 +31,7 @@ #define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS)) #define FTYPE_BUILDABLE(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) #define FTYPE_BUILDABLE_L(tp) (FTYPE_BUILDABLE(tp) && (tp&(GAME_TMD))) +#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) #define FTYPE_HSINJECTABLE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) #define FTYPE_RESTORABLE(tp) (tp&(IMG_NAND)) diff --git a/source/game/cia.c b/source/game/cia.c index b4a022f..9d7144a 100644 --- a/source/game/cia.c +++ b/source/game/cia.c @@ -17,7 +17,7 @@ u32 ValidateCiaHeader(CiaHeader* header) { } u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) { - memcpy(info, header, 0x20); // take over first 0x20 byte + if ((u8*) info != (u8*) header) memcpy(info, header, 0x20); // take over first 0x20 byte info->offset_cert = align(header->size_header, 64); info->offset_ticket = info->offset_cert + align(header->size_cert, 64); diff --git a/source/game/game.h b/source/game/game.h index 4e4e508..af40958 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -7,4 +7,5 @@ #include "romfs.h" #include "firm.h" #include "boss.h" +#include "smdh.h" #include "ncchinfo.h" diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 33da816..8e3aada 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -1,5 +1,6 @@ #include "gameutil.h" #include "game.h" +#include "hid.h" #include "ui.h" #include "fsperm.h" #include "filetype.h" @@ -191,6 +192,7 @@ u32 LoadExeFsFile(void* data, const char* path, u32 offset, const char* name, u3 break; } } + if (exefile) { u32 size_exefile = exefile->size; u32 offset_exefile = (ncch.offset_exefs * NCCH_MEDIA_UNIT) + sizeof(ExeFsHeader) + exefile->offset; @@ -1374,6 +1376,105 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { return ret; } +u32 ShowSmdhTitleInfo(Smdh* smdh) { + const u32 lwrap = 24; + u8* icon = (u8*) (TEMP_BUFFER + sizeof(Smdh)); + char* desc_l = (char*) icon + SMDH_SIZE_ICON_BIG; + char* desc_s = (char*) desc_l + SMDH_SIZE_DESC_LONG; + char* pub = (char*) desc_s + SMDH_SIZE_DESC_SHORT; + if ((GetSmdhIconBig(icon, smdh) != 0) || + (GetSmdhDescLong(desc_l, smdh) != 0) || + (GetSmdhDescShort(desc_s, smdh) != 0) || + (GetSmdhPublisher(pub, smdh) != 0)) + return 1; + WordWrapString(desc_l, lwrap); + WordWrapString(desc_s, lwrap); + WordWrapString(pub, lwrap); + ShowIconString(icon, SMDH_DIM_ICON_BIG, SMDH_DIM_ICON_BIG, "%s\n%s\n%s", desc_l, desc_s, pub); + InputWait(); + ClearScreenF(true, false, COLOR_STD_BG); + return 0; +} + +u32 ShowSmdhFileTitleInfo(const char* path) { + Smdh* smdh = (Smdh*) (void*) TEMP_BUFFER; + UINT btr; + if ((fvx_qread(path, smdh, 0, sizeof(Smdh), &btr) != FR_OK) || (btr != sizeof(Smdh))) + return 1; + return ShowSmdhTitleInfo(smdh); +} + +u32 ShowNcchFileTitleInfo(const char* path) { + Smdh* smdh = (Smdh*) (void*) TEMP_BUFFER; + if (LoadExeFsFile(smdh, path, 0, "icon", sizeof(Smdh)) != 0) + return 1; + return ShowSmdhTitleInfo(smdh); +} + +u32 ShowNcsdFileTitleInfo(const char* path) { + Smdh* smdh = (Smdh*) (void*) TEMP_BUFFER; + if (LoadExeFsFile(smdh, path, NCSD_CNT0_OFFSET, "icon", sizeof(Smdh)) != 0) + return 1; + return ShowSmdhTitleInfo(smdh); +} + +u32 ShowCiaFileTitleInfo(const char* path) { + Smdh* smdh = (Smdh*) (void*) TEMP_BUFFER; + CiaInfo info; + UINT btr; + + if ((fvx_qread(path, &info, 0, 0x20, &btr) != FR_OK) || (btr != 0x20) || + (GetCiaInfo(&info, (CiaHeader*) &info) != 0)) + return 1; + if ((info.offset_meta) && ((fvx_qread(path, smdh, info.offset_meta + 0x400, sizeof(Smdh), &btr) != FR_OK) || + (btr != sizeof(Smdh)))) return 1; + else if (LoadExeFsFile(smdh, path, info.offset_content, "icon", sizeof(Smdh)) != 0) return 1; + + return ShowSmdhTitleInfo(smdh); +} + +u32 ShowTmdFileTitleInfo(const char* path) { + const u8 dlc_tid_high[] = { DLC_TID_HIGH }; + TitleMetaData* tmd = (TitleMetaData*) TEMP_BUFFER; + TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1); + + // content path string + char path_content[256]; + char* name_content; + strncpy(path_content, path, 256); + name_content = strrchr(path_content, '/'); + if (!name_content) return 1; // will not happen + name_content++; + + // load TMD file + if ((LoadTmdFile(tmd, path) != 0) || !getbe16(tmd->content_count)) + return 1; + snprintf(name_content, 256 - (name_content - path_content), + (memcmp(tmd->title_id, dlc_tid_high, sizeof(dlc_tid_high)) == 0) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id)); + + return ShowGameFileTitleInfo(path_content); +} + +u32 ShowGameFileTitleInfo(const char* path) { + u32 filetype = IdentifyFileType(path); + u32 ret = 1; + + // build CIA from game file + if (filetype & GAME_SMDH) { + ret = ShowSmdhFileTitleInfo(path); + } else if (filetype & GAME_NCCH) { + ret = ShowNcchFileTitleInfo(path); + } else if (filetype & GAME_NCSD) { + ret = ShowNcsdFileTitleInfo(path); + } else if (filetype & GAME_CIA) { + ret = ShowCiaFileTitleInfo(path); + } else if (filetype & GAME_TMD) { + ret = ShowTmdFileTitleInfo(path); + } + + return ret; +} + u32 BuildNcchInfoXorpads(const char* destdir, const char* path) { FIL fp_info; FIL fp_xorpad; diff --git a/source/game/gameutil.h b/source/game/gameutil.h index 31e1d21..176c27b 100644 --- a/source/game/gameutil.h +++ b/source/game/gameutil.h @@ -8,6 +8,7 @@ u32 VerifyGameFile(const char* path); u32 CheckEncryptedGameFile(const char* path); u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 BuildCiaFromGameFile(const char* path, bool force_legit); +u32 ShowGameFileTitleInfo(const char* path); u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 CheckHealthAndSafetyInject(const char* hsdrv); u32 InjectHealthAndSafety(const char* path, const char* destdrv); diff --git a/source/game/smdh.c b/source/game/smdh.c new file mode 100644 index 0000000..ce133dc --- /dev/null +++ b/source/game/smdh.c @@ -0,0 +1,61 @@ +#include "smdh.h" + +#define SMDH_STRING(str, src, len) for (u32 i = 0; i < len; i++) str[i] = src[i] +// shamelessly stolen from bch2obj.py / 3ds_hb_menu :) +#define SMDH_LUT 0, 1, 8, 9, 2, 3, 10, 11, 16, 17, 24, 25, 18, 19, 26, 27, \ + 4, 5, 12, 13, 6, 7, 14, 15, 20, 21, 28, 29, 22, 23, 30, 31, \ + 32, 33, 40, 41, 34, 35, 42, 43, 48, 49, 56, 57, 50, 51, 58, 59, \ + 36, 37, 44, 45, 38, 39, 46, 47, 52, 53, 60, 61, 54, 55, 62, 63 + +u32 ConvertSmdhIcon(u8* icon, const u16* smdh_icon, u32 w, u32 h) { + const u32 lut[8*8] = { SMDH_LUT }; + u16* pix565 = (u16*) smdh_icon; + for (u32 y = 0; y < h; y += 8) { + for (u32 x = 0; x < w; x += 8) { + for (u32 i = 0; i < 8*8; i++) { + u32 ix = x + (lut[i] & 0x7); + u32 iy = y + (lut[i] >> 3); + u8* pix888 = icon + ((iy * w) + ix) * 3; + *(pix888++) = ((*pix565 >> 0) & 0x1F) << 3; // B + *(pix888++) = ((*pix565 >> 5) & 0x3F) << 2; // G + *(pix888++) = ((*pix565 >> 11) & 0x1F) << 3; // R + pix565++; + } + } + } + return 0; +} + +// short desc is max 64(+1) chars long +u32 GetSmdhDescShort(char* desc, const Smdh* smdh) { + const SmdhAppTitle* title = &(smdh->apptitles[1]); // english title + memset(desc, 0, SMDH_SIZE_DESC_SHORT + 1); + SMDH_STRING(desc, title->short_desc, SMDH_SIZE_DESC_SHORT); + return 0; +} + +// long desc is max 128(+1) chars long +u32 GetSmdhDescLong(char* desc, const Smdh* smdh) { + const SmdhAppTitle* title = &(smdh->apptitles[1]); // english title + memset(desc, 0, SMDH_SIZE_DESC_LONG + 1); + SMDH_STRING(desc, title->long_desc, SMDH_SIZE_DESC_LONG); + return 0; +} + +// publisher is max 64(+1) chars long +u32 GetSmdhPublisher(char* pub, const Smdh* smdh) { + const SmdhAppTitle* title = &(smdh->apptitles[1]); // english title + memset(pub, 0, SMDH_SIZE_PUBLISHER + 1); + SMDH_STRING(pub, title->publisher, SMDH_SIZE_PUBLISHER); + return 0; +} + +// small icons are 24x24 => 0x6C0 byte in RGB888 +u32 GetSmdhIconSmall(u8* icon, const Smdh* smdh) { + return ConvertSmdhIcon(icon, smdh->icon_small, SMDH_DIM_ICON_SMALL, SMDH_DIM_ICON_SMALL); +} + +// big icons are 48x48 => 0x1B00 byte in RGB888 +u32 GetSmdhIconBig(u8* icon, const Smdh* smdh) { + return ConvertSmdhIcon(icon, smdh->icon_big, SMDH_DIM_ICON_BIG, SMDH_DIM_ICON_BIG); +} diff --git a/source/game/smdh.h b/source/game/smdh.h new file mode 100644 index 0000000..82899a7 --- /dev/null +++ b/source/game/smdh.h @@ -0,0 +1,45 @@ +#pragma once + +#include "common.h" + +#define SMDH_MAGIC 'S', 'M', 'D', 'H' +#define SMDH_SIZE_DESC_SHORT 64 +#define SMDH_SIZE_DESC_LONG 128 +#define SMDH_SIZE_PUBLISHER 64 +#define SMDH_DIM_ICON_SMALL 24 +#define SMDH_DIM_ICON_BIG 48 +#define SMDH_SIZE_ICON_SMALL (SMDH_DIM_ICON_SMALL * SMDH_DIM_ICON_SMALL * 3) // w * h * bpp (rgb888) +#define SMDH_SIZE_ICON_BIG (SMDH_DIM_ICON_BIG * SMDH_DIM_ICON_BIG * 3) // w * h * bpp (rgb888) + +// see: https://www.3dbrew.org/wiki/SMDH#Application_Titles +typedef struct { + u16 short_desc[0x40]; + u16 long_desc[0x80]; + u16 publisher[0x40]; +} __attribute__((packed)) SmdhAppTitle; + +// see: https://www.3dbrew.org/wiki/SMDH +typedef struct { + char magic[4]; + u16 version; + u16 reserved0; + SmdhAppTitle apptitles[0x10]; // 1 -> english title + u8 game_ratings[0x10]; + u32 region_lockout; + u32 matchmaker_id; + u64 matchmaker_id_bit; + u32 flags; + u16 version_eula; + u16 reserved1; + u32 anim_def_frame; + u32 cec_id; + u64 reserved2; + u16 icon_small[0x240]; // 24x24x16bpp / 8x8 tiles / rgb565 + u16 icon_big[0x900]; // 48x48x16bpp / 8x8 tiles / rgb565 +} __attribute__((packed)) Smdh; + +u32 GetSmdhDescShort(char* desc, const Smdh* smdh); +u32 GetSmdhDescLong(char* desc, const Smdh* smdh); +u32 GetSmdhPublisher(char* pub, const Smdh* smdh); +u32 GetSmdhIconSmall(u8* icon, const Smdh* smdh); +u32 GetSmdhIconBig(u8* icon, const Smdh* smdh); diff --git a/source/godmode.c b/source/godmode.c index 9bf2c66..dc31e41 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -653,14 +653,15 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (drvtype & DRV_FAT)); bool buildable = (FTYPE_BUILDABLE(filetype)); bool buildable_legit = (FTYPE_BUILDABLE_L(filetype)); + bool titleinfo = (FTYPE_TITLEINFO(filetype)); bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_A9LH && (drvtype & DRV_FAT)); bool hsinjectable = (FTYPE_HSINJECTABLE(filetype)); bool restorable = (FTYPE_RESTORABLE(filetype) && IS_A9LH && !(drvtype & DRV_SYSNAND)); bool ebackupable = (FTYPE_EBACKUP(filetype)); bool xorpadable = (FTYPE_XORPAD(filetype)); bool launchable = ((FTYPE_PAYLOAD(filetype)) && (drvtype & DRV_FAT)); - bool special_opt = mountable || verificable || decryptable || encryptable || - buildable || buildable_legit || hsinjectable || restorable || xorpadable || launchable || ebackupable; + bool special_opt = mountable || verificable || decryptable || encryptable || buildable || buildable_legit || + titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -695,6 +696,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & GAME_TMD ) ? "TMD file options..." : (filetype & GAME_BOSS ) ? "BOSS file options..." : (filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" : + (filetype & GAME_SMDH) ? "Show SMDH title info" : (filetype & SYS_FIRM ) ? "FIRM image options..." : (filetype & SYS_TICKDB) ? "Mount as ticket.db" : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." : @@ -786,6 +788,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur // stuff for special menu starts here n_opt = 0; + int show_info = (titleinfo) ? ++n_opt : -1; int mount = (mountable) ? ++n_opt : -1; int restore = (restorable) ? ++n_opt : -1; int ebackup = (ebackupable) ? ++n_opt : -1; @@ -802,6 +805,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (mount > 0) optionstr[mount-1] = "Mount image to drive"; if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)"; if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup"; + if (show_info > 0) optionstr[show_info-1] = "Show title info"; if (decrypt > 0) optionstr[decrypt-1] = (cryptable_inplace) ? "Decrypt file (...)" : "Decrypt file (" OUTPUT_PATH ")"; if (encrypt > 0) optionstr[encrypt-1] = (cryptable_inplace) ? "Encrypt file (...)" : "Encrypt file (" OUTPUT_PATH ")"; if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; @@ -993,6 +997,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed"); } return 0; + } else if (user_select == show_info) { // -> Show title info + if (ShowGameFileTitleInfo(curr_entry->path) != 0) + ShowPrompt(false, "Title info: not found"); + return 0; } else if (user_select == hsinject) { // -> Inject to Health & Safety char* destdrv[2] = { NULL }; n_opt = 0;