diff --git a/source/game/nds.c b/source/game/nds.c index 1f46317..58725f8 100644 --- a/source/game/nds.c +++ b/source/game/nds.c @@ -3,6 +3,11 @@ #define CRC16_TABVAL 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400 +#define FNT_ENTRY_ISDIR(e) ((bool)((*(u8*)(e))&0x80)) +#define FNT_ENTRY_FNLEN(e) ((*(u8*)(e))&~0x80) +#define FNT_ENTRY_LEN(e) (1 + FNT_ENTRY_FNLEN(e) + (FNT_ENTRY_ISDIR(e)?2:0)) +#define FNT_ENTRY_NEXT(e) (((u8*)(e)) + FNT_ENTRY_LEN(e)) + typedef struct { u32 subtable_offset; @@ -89,47 +94,62 @@ u32 GetTwlIcon(u8* icon, const TwlIconData* twl_icon) { return 0; } -u32 ReadNitroRomDir(u32 dirid, u64* offset, u64* size, bool* is_dir, u8** fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat) { - static u32 fileid = 0; - static u8* subtbl_end = NULL; +u32 FindNitroRomDir(u32 dirid, u32* fileid, u8** fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat) { NitroFntBaseEntry* fnt_base = (NitroFntBaseEntry*) fnt; NitroFntBaseEntry* fnt_dir = &((NitroFntBaseEntry*) fnt)[dirid]; NitroFatEntry* fat_lut = (NitroFatEntry*) fat; - if (dirid >= fnt_base->parent_id) return 1; // dir ID out of bounds - if (*fnt_entry && (*fnt_entry - fnt >= (int) hdr->fnt_size)) return 1; // FNT entry out of bounds + // base sanity checks if (fnt_base->parent_id*sizeof(NitroFntBaseEntry) > fnt_base->subtable_offset) return 1; // invalid FNT + if (dirid >= fnt_base->parent_id) return 1; // dir ID out of bounds - if (!*fnt_entry) { // if *fnt_entry is NULL: reset file id and start with first entry - *fnt_entry = fnt + fnt_dir->subtable_offset; - fileid = fnt_dir->file0_id; - for (subtbl_end = *fnt_entry; *subtbl_end && (subtbl_end < fnt + hdr->fnt_size); subtbl_end++); - } else { // advance to next entry - u32 pfnlen = **fnt_entry & ~0x80; - bool was_dir = **fnt_entry & 0x80; - *fnt_entry += 1 + pfnlen + (was_dir?2:0); - if (!was_dir) fileid++; + // set first FNT entry / fileid + *fnt_entry = fnt + fnt_dir->subtable_offset; + *fileid = fnt_dir->file0_id; + + // check subtable / directory validity + if (*fnt_entry >= fnt + hdr->fnt_size) return 1; + u8* subtbl_end = NULL; + u32 fid = *fileid; + for (subtbl_end = *fnt_entry; *subtbl_end && (subtbl_end < fnt + hdr->fnt_size); subtbl_end++); + for (u8* entry = *fnt_entry; *entry; entry = FNT_ENTRY_NEXT(entry)) { + if (entry > subtbl_end) return 1; // corrupt subtable + if (fat_lut[fid].start_address > fat_lut[fid].end_address) return 1; // corrupt fat + if (!FNT_ENTRY_ISDIR(entry)) fid++; } + if (fid*sizeof(NitroFatEntry) > hdr->fat_size) return 1; // corrupt fnt / fat - // check for trouble - if (*fnt_entry > subtbl_end) - return 1; + + return 0; +} + +u32 NextNitroRomEntry(u32* fileid, u8** fnt_entry) { + // check for end of subtable + if (!*fnt_entry || !**fnt_entry) return 1; + + // advance to next entry + if (!FNT_ENTRY_ISDIR(*fnt_entry)) (*fileid)++; + *fnt_entry += FNT_ENTRY_LEN(*fnt_entry); // check for end of subtable - if (!**fnt_entry) { // end of subtable reached - *fnt_entry = NULL; - return 0; - } + if (!**fnt_entry) return 1; - *is_dir = **fnt_entry & 0x80; + return 0; +} + +u32 ReadNitroRomEntry(u64* offset, u64* size, bool* is_dir, u32 fileid, u8* fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat) { + // check for end of subtable + if (!fnt_entry || !*fnt_entry) return 1; + + // decipher FNT entry + *is_dir = FNT_ENTRY_ISDIR(fnt_entry); if (!(*is_dir)) { // for files - if (fileid*sizeof(NitroFatEntry) > hdr->fat_size) return 1; // corrupt fnt / fat - if (fat_lut[fileid].start_address > fat_lut[fileid].end_address) return 1; // corrupt fat + NitroFatEntry* fat_lut = (NitroFatEntry*) fat; *offset = fat_lut[fileid].start_address; *size = fat_lut[fileid].end_address - fat_lut[fileid].start_address; } else { // for dirs - u32 fnlen = **fnt_entry & ~0x80; - *offset = (u64) ((*fnt_entry)[1+fnlen]|((*fnt_entry)[1+fnlen+1]<<8)) & 0xFFF; // dir ID goes in offset + u32 fnlen = FNT_ENTRY_FNLEN(fnt_entry); + *offset = (u64) (fnt_entry[1+fnlen]|(fnt_entry[1+fnlen+1]<<8)) & 0xFFF; // dir ID goes in offset *size = 0; } diff --git a/source/game/nds.h b/source/game/nds.h index dfd73be..5e0cfec 100644 --- a/source/game/nds.h +++ b/source/game/nds.h @@ -127,4 +127,7 @@ u32 ValidateTwlHeader(TwlHeader* twl); u32 LoadTwlMetaData(const char* path, TwlHeader* hdr, TwlIconData* icon); u32 GetTwlTitle(char* desc, const TwlIconData* twl_icon); u32 GetTwlIcon(u8* icon, const TwlIconData* twl_icon); -u32 ReadNitroRomDir(u32 dirid, u64* offset, u64* size, bool* is_dir, u8** fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat); + +u32 FindNitroRomDir(u32 dirid, u32* fileid, u8** fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat); +u32 NextNitroRomEntry(u32* fileid, u8** fnt_entry); +u32 ReadNitroRomEntry(u64* offset, u64* size, bool* is_dir, u32 fileid, u8* fnt_entry, TwlHeader* hdr, u8* fnt, u8* fat); diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c index c69feb1..f6b5fdc 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -797,33 +797,41 @@ bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) { } bool ReadVGameDirNitro(VirtualFile* vfile, VirtualDir* vdir) { - static u8* fnt_entry = NULL; + u8* fnt = nitrofs; + u8* fat = nitrofs + twl->fat_offset - twl->fnt_offset; + vfile->name[0] = '\0'; vfile->flags = VFLAG_NITRO; vfile->keyslot = 0; // start from parent dir object if (vdir->index == -1) { - fnt_entry = NULL; - vdir->index = 0; + u8* fnt_entry = NULL; + u32 dirid = vdir->offset & 0xFFF; + u32 fileid = 0; + if (FindNitroRomDir(dirid, &fileid, &fnt_entry, twl, fnt, fat) == 0) { + vdir->index = fileid; // store fileid in index + vdir->offset = (vdir->offset&0xFFFFFFFF) | (((u64)(fnt_entry - fnt)) << 32); // store offsets in offset + } else vdir->index = -3; // error } // read directory entries until done - if (vdir->index == 0) { - u8* fnt = nitrofs; - u8* fat = nitrofs + twl->fat_offset - twl->fnt_offset; - u32 dirid = vdir->offset & 0xFFF; + if (vdir->index >= 0) { + u8* fnt_entry = fnt + (vdir->offset >> 32); + u32 fileid = vdir->index; bool is_dir; - if (ReadNitroRomDir(dirid, &(vfile->offset), &(vfile->size), &is_dir, &fnt_entry, twl, fnt, fat) != 0) - vdir->index = 2; // error reading dir - if (fnt_entry) { + if (ReadNitroRomEntry(&(vfile->offset), &(vfile->size), &is_dir, fileid, fnt_entry, twl, fnt, fat) == 0) { if (!is_dir) vfile->offset += offset_nds; vfile->offset |= ((u64)(fnt_entry - fnt)) << 32; if (is_dir) vfile->flags |= VFLAG_DIR; - } else vdir->index = 1; // end of dir + // advance to next entry + NextNitroRomEntry(&fileid, &fnt_entry); + vdir->index = fileid; + vdir->offset = (vdir->offset&0xFFFFFFFF) | (((u64)(fnt_entry - fnt)) << 32); + } else vdir->index = -2; // end of dir } - return (vdir->index == 0); + return (vdir->index >= 0); } bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir) {