diff --git a/source/common.h b/source/common.h index 63d4f8e..c8baba8 100644 --- a/source/common.h +++ b/source/common.h @@ -38,7 +38,7 @@ (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) // GodMode9 version -#define VERSION "0.8.8" +#define VERSION "0.8.9" // input / output paths #define INPUT_PATHS "0:", "0:/files9", "0:/Decrypt9" diff --git a/source/fs/filetype.h b/source/fs/filetype.h index 85f50f2..d50a203 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -15,7 +15,7 @@ #define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS) #define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD) #define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH) -#define FTYPE_BUILDABLE (GAME_TMD) +#define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD) #define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD)) u32 IdentifyFileType(const char* path); diff --git a/source/game/cia.c b/source/game/cia.c index 23ff012..cc2787e 100644 --- a/source/game/cia.c +++ b/source/game/cia.c @@ -102,7 +102,7 @@ u32 BuildCiaCert(u8* ciacert) { return 0; } -u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { +u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size) { const u8 sig_type[4] = { TMD_SIG_TYPE }; // safety check: number of contents if (n_contents > CIA_MAX_CONTENTS) return 1; // !!! @@ -115,7 +115,7 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { tmd->version = 0x01; memcpy(tmd->title_id, title_id, 8); tmd->title_type[3] = 0x40; // whatever - memset(tmd->save_size, 0x00, 4); // placeholder + for (u32 i = 0; i < 4; i++) tmd->save_size[i] = (save_size >> (i*8)) & 0xFF; // little endian? tmd->content_count[1] = (u8) n_contents; memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash) tmd->contentinfo[0].cmd_count[1] = (u8) n_contents; @@ -125,12 +125,12 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { return 0; } -u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh) { +u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh) { // init metadata with all zeroes and core version memset(meta, 0x00, sizeof(CiaMeta)); meta->core_version = 2; // copy dependencies from extheader - if (exthdr) memcpy(meta->dependencies, exthdr + 0x40, sizeof(meta->dependencies)); + if (exthdr) memcpy(meta->dependencies, ((NcchExtHeader*) exthdr)->dependencies, sizeof(meta->dependencies)); // copy smdh (icon file in exefs) if (smdh) memcpy(meta->smdh, smdh, sizeof(meta->smdh)); return 0; diff --git a/source/game/cia.h b/source/game/cia.h index 7e33ae6..5dc57a2 100644 --- a/source/game/cia.h +++ b/source/game/cia.h @@ -118,8 +118,8 @@ u32 FixTmdHashes(TitleMetaData* tmd); u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd); u32 BuildCiaCert(u8* ciacert); -u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents); -u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh); +u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size); +u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh); u32 BuildCiaHeader(CiaHeader* header); u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey); diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 1002e6e..357cb73 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -35,7 +35,7 @@ u32 GetOutputPath(char* dest, const char* path, const char* ext) { return 0; } -u32 GetNcchHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) { +u32 GetNcchHeaders(NcchHeader* ncch, NcchExtHeader* exthdr, ExeFsHeader* exefs, FIL* file) { u32 offset_ncch = f_tell(file); UINT btr; @@ -43,7 +43,16 @@ u32 GetNcchHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) { (ValidateNcchHeader(ncch) != 0)) return 1; - if (exefs && ncch->size_exefs) { + if (exthdr) { + if(!ncch->size_exthdr) return 1; + f_lseek(file, offset_ncch + NCCH_EXTHDR_OFFSET); + if ((fx_read(file, exthdr, NCCH_EXTHDR_SIZE, &btr) != FR_OK) || + (DecryptNcch((u8*) exthdr, NCCH_EXTHDR_OFFSET, NCCH_EXTHDR_SIZE, ncch, NULL) != 0)) + return 1; + } + + if (exefs) { + if (!ncch->size_exefs) return 1; u32 offset_exefs = offset_ncch + (ncch->offset_exefs * NCCH_MEDIA_UNIT); f_lseek(file, offset_exefs); if ((fx_read(file, exefs, sizeof(ExeFsHeader), &btr) != FR_OK) || @@ -72,6 +81,22 @@ u32 CheckNcchHash(u8* expected, FIL* file, u32 size_data, u32 offset_ncch, NcchH return (memcmp(hash, expected, 32) == 0) ? 0 : 1; } +u32 LoadNcchHeaders(NcchHeader* ncch, NcchExtHeader* exthdr, ExeFsHeader* exefs, const char* path, u32 offset) { + FIL file; + + // open file, get NCCH header + if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + f_lseek(&file, offset); + if (GetNcchHeaders(ncch, exthdr, exefs, &file) != 0) { + fx_close(&file); + return 1; + } + fx_close(&file); + + return 0; +} + u32 LoadNcsdHeader(NcsdHeader* ncsd, const char* path) { FIL file; UINT btr; @@ -118,67 +143,62 @@ u32 LoadCiaStub(CiaStub* stub, const char* path) { return 0; } -u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) { +u32 LoadExeFsFile(void* data, const char* path, u32 offset, const char* name, u32 size_max) { NcchHeader ncch; ExeFsHeader exefs; FIL file; UINT btr; u32 ret = 0; - // this uses the meta builder function only in part - if (BuildCiaMeta(meta, NULL, NULL) != 0) return 1; - // open file, get NCCH, ExeFS header if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; f_lseek(&file, offset); - if (GetNcchHeaders(&ncch, &exefs, &file) != 0) { + if ((GetNcchHeaders(&ncch, NULL, &exefs, &file) != 0) || + (!ncch.size_exefs)) { fx_close(&file); return 1; } - // dependencies - if (ncch.size_exthdr > 0) { - u8* dep = meta->dependencies; - u32 size_dep = sizeof(meta->dependencies); - f_lseek(&file, offset + NCCH_EXTHDR_OFFSET + 0x40); // offset to dependencies - if ((fx_read(&file, dep, size_dep, &btr) != FR_OK) || - (DecryptNcch(dep, NCCH_EXTHDR_OFFSET + 0x40, size_dep, &ncch, NULL) != 0) || - (btr != size_dep)) { + // load file from exefs + ExeFsFileHeader* exefile = NULL; + for (u32 i = 0; i < 10; i++) { + u32 size = exefs.files[i].size; + if (!size || (size > size_max)) continue; + char* exename = exefs.files[i].name; + if (strncmp(name, exename, 8) == 0) { + exefile = exefs.files + i; + break; + } + } + if (exefile) { + u32 size_exefile = exefile->size; + u32 offset_exefile = (ncch.offset_exefs * NCCH_MEDIA_UNIT) + sizeof(ExeFsHeader) + exefile->offset; + f_lseek(&file, offset + offset_exefile); // offset to file + if ((fx_read(&file, data, size_exefile, &btr) != FR_OK) || + (DecryptNcch(data, offset_exefile, size_exefile, &ncch, &exefs) != 0) || + (btr != size_exefile)) { ret = 1; } } else ret = 1; - // smdh from exefs - if (ncch.size_exefs > 0) { - ExeFsFileHeader* icon = NULL; - u32 size_smdh = sizeof(meta->smdh); - for (u32 i = 0; i < 10; i++) { - u32 size = exefs.files[i].size; - if (!size || (size > size_smdh)) continue; - char* name = exefs.files[i].name; - if (strncmp(name, "icon", 8) == 0) { - icon = exefs.files + i; - break; - } - } - if (icon) { - u32 size_icon = icon->size; - u32 offset_icon = (ncch.offset_exefs * NCCH_MEDIA_UNIT) + sizeof(ExeFsHeader) + icon->offset; - u8* smdh = meta->smdh; - f_lseek(&file, offset + offset_icon); // offset to icon - if ((fx_read(&file, smdh, size_icon, &btr) != FR_OK) || - (DecryptNcch(smdh, offset_icon, size_icon, &ncch, &exefs) != 0) || - (btr != size_icon)) { - ret = 1; - } - } else ret = 1; - } else ret = 1; - fx_close(&file); return ret; } +u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) { + NcchHeader ncch; + NcchExtHeader exthdr; + + // get dependencies from exthdr, icon from exeFS + if ((LoadNcchHeaders(&ncch, &exthdr, NULL, path, offset) != 0) || + (BuildCiaMeta(meta, &exthdr, NULL) != 0) || + (LoadExeFsFile(meta->smdh, path, offset, "icon", sizeof(meta->smdh)))) + return 1; + + return 0; +} + u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { const u8 magic[] = { TMD_SIG_TYPE }; FIL file; @@ -267,7 +287,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) { return 1; f_lseek(&file, offset); - if (GetNcchHeaders(&ncch, &exefs, &file) != 0) { + if (GetNcchHeaders(&ncch, NULL, &exefs, &file) != 0) { if (!offset) ShowPrompt(false, "%s\nError: Not a NCCH file", pathstr); fx_close(&file); return 1; @@ -669,7 +689,7 @@ u32 DecryptGameFile(const char* path, bool inplace) { } u32 InsertCiaContent(const char* path_cia, const char* path_content, u32 offset, u32 size, - TmdContentChunk* chunk, const u8* titlekey, bool force_legit) { + TmdContentChunk* chunk, const u8* titlekey, bool force_legit, bool cxi_fix) { // crypto types bool ncch_crypto = (!force_legit && (CheckEncryptedNcchFile(path_content, offset) == 0)); bool cia_crypto = (force_legit && (getbe16(chunk->type) & 0x01)); @@ -683,7 +703,9 @@ u32 InsertCiaContent(const char* path_cia, const char* path_content, u32 offset, if (fx_open(&ofile, path_content, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; f_lseek(&ofile, offset); - fsize = f_size(&ofile); // for progress bar + fsize = f_size(&ofile); + if (offset > fsize) return 1; + if (!size) size = fsize - offset; if (fx_open(&dfile, path_cia, FA_WRITE | FA_OPEN_APPEND) != FR_OK) { fx_close(&ofile); return 1; @@ -703,12 +725,13 @@ u32 InsertCiaContent(const char* path_cia, const char* path_content, u32 offset, u8 ctr[16]; u32 ret = 0; GetTmdCtr(ctr, chunk); - sha_init(SHA256_MODE); if (!ShowProgress(0, 0, path_content)) ret = 1; for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); if (fx_read(&ofile, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; if (ncch_crypto && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) ret = 1; + if ((i == 0) && cxi_fix && (SetNcchSdFlag(MAIN_BUFFER) != 0)) ret = 1; + if (i == 0) sha_init(SHA256_MODE); sha_update(MAIN_BUFFER, read_bytes); if (cia_crypto && (EncryptCiaContentSequential(MAIN_BUFFER, read_bytes, ctr, titlekey) != 0)) ret = 1; if (fx_write(&dfile, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = 1; @@ -800,7 +823,7 @@ u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_l for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { TmdContentChunk* chunk = &(content_list[i]); snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(chunk->id)); - if (InsertCiaContent(path_cia, path_content, 0, (u32) getbe64(chunk->size), chunk, titlekey, force_legit) != 0) { + if (InsertCiaContent(path_cia, path_content, 0, (u32) getbe64(chunk->size), chunk, titlekey, force_legit, false) != 0) { ShowPrompt(false, "ID %016llX.%08lX\nInsert content failed", getbe64(title_id), getbe32(chunk->id)); return 1; } @@ -820,6 +843,125 @@ u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_l return 0; } +u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) { + CiaStub* cia = (CiaStub*) TEMP_BUFFER; + CiaMeta* meta = (CiaMeta*) (void*) (cia + 1); + NcchExtHeader* exthdr = (NcchExtHeader*) (void*) (meta + 1); + NcchHeader ncch; + u8 title_id[8]; + u32 save_size = 0; + + // Init progress bar + if (!ShowProgress(0, 0, path_ncch)) return 1; + + // load NCCH header / extheader, get save size && title id + if (LoadNcchHeaders(&ncch, exthdr, NULL, path_ncch, 0) == 0) { + save_size = getle32(exthdr->sys_info); + } else { + exthdr = NULL; + if (LoadNcchHeaders(&ncch, NULL, NULL, path_ncch, 0) != 0) return 1; + } + for (u32 i = 0; i < 8; i++) + title_id[i] = (ncch.programId >> ((7-i)*8)) & 0xFF; + + // build the CIA stub + memset(cia, 0, sizeof(CiaStub)); + if ((BuildCiaHeader(&(cia->header)) != 0) || + (BuildCiaCert(cia->cert) != 0) || + (BuildFakeTicket(&(cia->ticket), title_id) != 0) || + (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size)) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_cia) != 0)) { + return 1; + } + + // insert NCCH content + TmdContentChunk* chunk = cia->content_list; + memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do + if (InsertCiaContent(path_cia, path_ncch, 0, 0, chunk, NULL, false, true) != 0) + return 1; + + // optional stuff (proper titlekey / meta data) + SearchTitleKeysBin((&cia->ticket), title_id); + if (exthdr && (BuildCiaMeta(meta, exthdr, NULL) == 0) && + (LoadExeFsFile(meta->smdh, path_ncch, 0, "icon", sizeof(meta->smdh)) == 0) && + (InsertCiaMeta(path_cia, meta) == 0)) + cia->header.size_meta = CIA_META_SIZE; + + // write the CIA stub (take #2) + if ((FixTmdHashes(&(cia->tmd)) != 0) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_cia) != 0)) + return 1; + + return 0; +} + +u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { + CiaStub* cia = (CiaStub*) TEMP_BUFFER; + CiaMeta* meta = (CiaMeta*) (void*) (cia + 1); + NcchExtHeader* exthdr = (NcchExtHeader*) (void*) (meta + 1); + NcsdHeader ncsd; + NcchHeader ncch; + u8 title_id[8]; + u32 save_size = 0; + + // Init progress bar + if (!ShowProgress(0, 0, path_ncsd)) return 1; + + // load NCSD header, get content count, title id + u32 content_count = 0; + if (LoadNcsdHeader(&ncsd, path_ncsd) != 0) return 1; + for (u32 i = 0; i < 3; i++) + if (ncsd.partitions[i].size) content_count++; + for (u32 i = 0; i < 8; i++) + title_id[i] = (ncsd.mediaId >> ((7-i)*8)) & 0xFF; + + // load first content NCCH / extheader + if (LoadNcchHeaders(&ncch, exthdr, NULL, path_ncsd, NCSD_CNT0_OFFSET) != 0) + return 1; + save_size = getle32(exthdr->sys_info); + + // build the CIA stub + memset(cia, 0, sizeof(CiaStub)); + if ((BuildCiaHeader(&(cia->header)) != 0) || + (BuildCiaCert(cia->cert) != 0) || + (BuildFakeTicket(&(cia->ticket), title_id) != 0) || + (BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size)) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_cia) != 0)) { + return 1; + } + + // insert NCSD content + TmdContentChunk* chunk = cia->content_list; + for (u32 i = 0; i < 3; i++) { + NcchPartition* partition = ncsd.partitions + i; + u32 offset = partition->offset * NCSD_MEDIA_UNIT; + u32 size = partition->size * NCSD_MEDIA_UNIT; + if (!size) continue; + memset(chunk, 0, sizeof(TmdContentChunk)); + chunk->id[3] = chunk->index[1] = i; + if (InsertCiaContent(path_cia, path_ncsd, offset, size, chunk++, NULL, false, (i == 0)) != 0) + return 1; + } + + // optional stuff (proper titlekey / meta data) + SearchTitleKeysBin(&(cia->ticket), title_id); + if ((BuildCiaMeta(meta, exthdr, NULL) == 0) && + (LoadExeFsFile(meta->smdh, path_ncsd, NCSD_CNT0_OFFSET, "icon", sizeof(meta->smdh)) == 0) && + (InsertCiaMeta(path_cia, meta) == 0)) + cia->header.size_meta = CIA_META_SIZE; + + // write the CIA stub (take #2) + if ((FixTmdHashes(&(cia->tmd)) != 0) || + (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || + (WriteCiaStub(cia, path_cia) != 0)) + return 1; + + return 0; +} + u32 BuildCiaFromGameFile(const char* path, bool force_legit) { u32 filetype = IdentifyFileType(path); char dest[256]; @@ -828,6 +970,7 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { // destination path if (GetOutputPath(dest, path, force_legit ? "legit.cia" : "cia") != 0) return 1; if (!CheckWritePermissions(dest)) return 1; + f_unlink(dest); // remove the file if it already exists // ensure the output dir exists if ((f_stat(OUTPUT_PATH, NULL) != FR_OK) && (f_mkdir(OUTPUT_PATH) != FR_OK)) @@ -836,6 +979,10 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { // build CIA from game file if (filetype & GAME_TMD) ret = BuildCiaFromTmdFile(path, dest, force_legit); + else if (filetype & GAME_NCCH) + ret = BuildCiaFromNcchFile(path, dest); + else if (filetype & GAME_NCSD) + ret = BuildCiaFromNcsdFile(path, dest); else ret = 1; if (ret != 0) // try to get rid of the borked file diff --git a/source/game/ncch.c b/source/game/ncch.c index ec23d93..9bc3268 100644 --- a/source/game/ncch.c +++ b/source/game/ncch.c @@ -318,3 +318,22 @@ u32 DecryptNcchSequential(u8* data, u32 offset, u32 size) { return DecryptNcch(data, offset, size, ncchptr, exefsptr); } + +u32 SetNcchSdFlag(u8* data) { // data must be at least 0x600 byte and start with NCCH header + NcchHeader* ncch = (NcchHeader*) (void*) data; + NcchExtHeader* exthdr = (NcchExtHeader*) (void*) (data + NCCH_EXTHDR_OFFSET); + NcchExtHeader exthdr_dec; + + if ((ValidateNcchHeader(ncch) != 0) || (!ncch->size_exthdr)) + return 0; // no extheader + memcpy(&exthdr_dec, exthdr, sizeof(NcchExtHeader)); + if (DecryptNcch((u8*) &exthdr_dec, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) != 0) + return 1; + if (exthdr_dec.flag & (1<<1)) return 0; // flag already set + + exthdr_dec.flag |= (1<<1); + exthdr->flag ^= (1<<1); + sha_quick(ncch->hash_exthdr, &exthdr_dec, 0x400, SHA256_MODE); + + return 0; +} diff --git a/source/game/ncch.h b/source/game/ncch.h index fad3ee6..c57d146 100644 --- a/source/game/ncch.h +++ b/source/game/ncch.h @@ -14,16 +14,16 @@ // very limited, contains only required stuff typedef struct { char name[8]; - u8 reserved[0x5]; - u8 flag; // bit 1 for SD - u32 remaster_version; - u8 sci_data[0x30]; - u8 dependencies[0x180]; - u8 sys_info[0x40]; - u8 aci_data[0x200]; - u8 signature[0x100]; - u8 public_key[0x100]; - u8 aci_limit_data[0x200]; + u8 reserved[0x5]; + u8 flag; // bit 1 for SD + u16 remaster_version; + u8 sci_data[0x30]; + u8 dependencies[0x180]; + u8 sys_info[0x40]; + u8 aci_data[0x200]; + u8 signature[0x100]; + u8 public_key[0x100]; + u8 aci_limit_data[0x200]; } __attribute__((packed)) NcchExtHeader; // see: https://www.3dbrew.org/wiki/NCCH#NCCH_Header @@ -63,3 +63,4 @@ u32 ValidateNcchHeader(NcchHeader* header); u32 SetupNcchCrypto(NcchHeader* ncch); u32 DecryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs); u32 DecryptNcchSequential(u8* data, u32 offset, u32 size); +u32 SetNcchSdFlag(u8* data); diff --git a/source/game/ncsd.h b/source/game/ncsd.h index dc1b567..04ad738 100644 --- a/source/game/ncsd.h +++ b/source/game/ncsd.h @@ -8,6 +8,7 @@ #define NCSD_CINFO_SIZE 0x1000 #define NCSD_DINFO_OFFSET 0x1200 #define NCSD_DINFO_SIZE 0x300 +#define NCSD_CNT0_OFFSET 0x4000 typedef struct { u32 offset; diff --git a/source/godmode.c b/source/godmode.c index 66ac6fb..c8c722f 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -562,6 +562,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur DirEntry* curr_entry = &(current_dir->entry[*cursor]); const char* optionstr[8]; + // check for file lock + if (!FileUnlock(curr_entry->path)) return 1; + u32 filetype = IdentifyFileType(curr_entry->path); u32 drvtype = DriveType(curr_entry->path); @@ -576,9 +579,6 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur char pathstr[32 + 1]; TruncateString(pathstr, curr_entry->path, 32, 8); - // check for file lock - if (!FileUnlock(curr_entry->path)) return 1; - // main menu processing int n_opt = 0; int special = (filetype && (drvtype & DRV_FAT)) ? ++n_opt : -1;