/* nitrofs.c - eris's wai ossum nitro filesystem device driver Based on information found at http://frangoassado.org/ds/rom_spec.txt and from the #dsdev ppls Kallisti (K) 2008-01-26 All rights reversed. 2008-05-19 v0.2 - New And Improved!! :DDD * fix'd the fseek SEEK_CUR issue (my fseek funct should not have returned a value :/) * also thx to wintermute's input realized: * if you dont give ndstool the -o wifilogo.bmp option it will run on emulators in gba mode * you then dont need the gba's LOADEROFFSET, so it was set to 0x000 2008-05-21 v0.3 - newer and more improved * fixed some issues with ftell() (again was fseek's fault u_u;;) * fixed possible error in detecting sc.gba files when using dldi * readded support for .gba files in addition to .nds emu * added stat() support for completedness :) 2008-05-22 v0.3.1 - slight update * again fixed fseek(), this time SEEK_END oddly i kinda forgot about it >_> sry * also went ahead and inlined the functions, makes slight proformance improvement 2008-05-26 v0.4 - added chdir * added proper chdir functionality 2008-05-30 v0.5.Turbo - major speed improvement * This version uses a single filehandle to access the .nds file when not in GBA mode improving the speed it takes to open a .nds file by around 106ms. This is great for situations requiring reading alot of seperate small files. However it does take a little bit longer when reading from multiple files simultainously (around 122ms over 10,327 0x100 byte reads between 2 files). 2008-06-09 * Fixed bug with SEEK_END where it wouldnt utilize the submitted position.. (now can fseek(f,-128,SEEK_END) to read from end of file :D) 2008-06-18 v0.6.Turbo - . and .. :D * Today i have added full "." and ".." support. dirnext() will return . and .. first, and all relevent operations will support . and .. in pathnames. 2009-05-10 v0.7.Turbo - small changes @_@?! 2009-08-08 v0.8.Turbo - fix fix fix * fixed problem with some cards where the header would be loaded to GBA ram even if running in NDS mode causing nitroFSInit() to think it was a valid GBA cart header and attempt to read from GBA SLOT instead of SLOT 1. Fixed this by making it check that filename is not NULL and then to try FAT/SLOT1 first. The NULL option allows forcing nitroFS to use gba. 2018-09-05 v0.9 - modernize devoptab (by RonnChyran) * Updated for libsysbase change in devkitARM r46 and above. */ #include #include #include #include "nitrofs.h" #include "tonccpy.h" //This seems to be a typo! memory.h has REG_EXEMEMCNT #ifndef REG_EXMEMCNT #define REG_EXMEMCNT (*(vuint16 *)0x04000204) #endif #define __itcm __attribute__((section(".itcm"))) //Globals! u32 fntOffset; //offset to start of filename table u32 fatOffset; //offset to start of file alloc table bool hasLoader; //single global nds filehandle (is null if not in dldi/fat mode) u16 chdirpathid; //default dir path id... FILE *ndsFile; off_t ndsFileLastpos; //Used to determine need to fseek or not devoptab_t nitroFSdevoptab = { "nitro", // const char *name; sizeof(struct nitroFSStruct), // int structSize; &nitroFSOpen, // int (*open_r)(struct _reent *r, void *fileStruct, const char *path,int flags,int mode); &nitroFSClose, // int (*close_r)(struct _reent *r,void* fd); NULL, // int (*write_r)(struct _reent *r,void* fd,const char *ptr,int len); &nitroFSRead, // int (*read_r)(struct _reent *r,void* fd,char *ptr,int len); &nitroFSSeek, // int (*seek_r)(struct _reent *r,void* fd,int pos,int dir); &nitroFSFstat, // int (*fstat_r)(struct _reent *r,void* fd,struct stat *st); &nitroFSstat, // int (*stat_r)(struct _reent *r,const char *file,struct stat *st); NULL, // int (*link_r)(struct _reent *r,const char *existing, const char *newLink); NULL, // int (*unlink_r)(struct _reent *r,const char *name); &nitroFSChdir, // int (*chdir_r)(struct _reent *r,const char *name); NULL, // int (*rename_r) (struct _reent *r, const char *oldName, const char *newName); NULL, // int (*mkdir_r) (struct _reent *r, const char *path, int mode); sizeof(struct nitroDIRStruct), // int dirStateSize; &nitroFSDirOpen, // DIR_ITER* (*diropen_r)(struct _reent *r, DIR_ITER *dirState, const char *path); &nitroDirReset, // int (*dirreset_r)(struct _reent *r, DIR_ITER *dirState); &nitroFSDirNext, // int (*dirnext_r)(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); &nitroFSDirClose // int (*dirclose_r)(struct _reent *r, DIR_ITER *dirState); }; //K, i decided to inline these, improves speed slightly.. //these 2 'sub' functions deal with actually reading from either gba rom or .nds file :) //what i rly rly rly wanna know is how an actual nds cart reads from itself, but it seems no one can tell me ~_~ //so, instead we have this weird weird haxy try gbaslot then try dldi method. If i (or you!!) ever do figure out //how to read the proper way can replace these 4 functions and everything should work normally :) //reads from rom image either gba rom or dldi inline ssize_t nitroSubRead(off_t *npos, void *ptr, size_t len) { if (ndsFile != NULL) { //read from ndsfile if (ndsFileLastpos != *npos) fseek(ndsFile, *npos, SEEK_SET); //if we need to, move! (might want to verify this succeed) len = fread(ptr, 1, len, ndsFile); } else { //reading from gbarom tonccpy(ptr, *npos + (void *)GBAROM, len); //len isnt checked here because other checks exist in the callers (hopefully) } if (len > 0) *npos += len; ndsFileLastpos = *npos; //save the current file nds pos return (len); } //seek around inline void nitroSubSeek(off_t *npos, int pos, int dir) { if ((dir == SEEK_SET) || (dir == SEEK_END)) //otherwise just set the pos :) *npos = pos; else if (dir == SEEK_CUR) *npos += pos; //see ez! } //Figure out if its gba or ds, setup stuff int __itcm nitroFSInit(const char *ndsfile) { off_t pos = 0; char romstr[0x10]; chdirpathid = NITROROOT; ndsFileLastpos = 0; ndsFile = NULL; bool noLoader = ((strncmp((const char *)0x02FFFC38, __NDSHeader->gameCode, 4) == 0) && (*(u16*)0x02FFFC36 == __NDSHeader->headerCRC16)); if (!isDSiMode() || noLoader) { sysSetCartOwner (BUS_OWNER_ARM9); //give us gba slot ownership if ((strncmp(((const char *)GBAROM) + LOADERSTROFFSET, LOADERSTR, strlen(LOADERSTR)) == 0) || noLoader) { // We has gba rahm //printf("yes i think this is GBA?!\n"); if ((strncmp(((const char *)GBAROM) + 0x20C, __NDSHeader->gameCode, 4) == 0) && (*(u16*)0x0800035E == __NDSHeader->headerCRC16)) { //Look for second magic string, if found its a sc.nds or nds.gba //printf("sc/gba\n"); fntOffset = ((u32) * (u32 *)(((const char *)GBAROM) + FNTOFFSET + LOADEROFFSET)) + LOADEROFFSET; fatOffset = ((u32) * (u32 *)(((const char *)GBAROM) + FATOFFSET + LOADEROFFSET)) + LOADEROFFSET; hasLoader = true; AddDevice(&nitroFSdevoptab); return (1); } else { //Ok, its not a .gba build, so must be emulator //printf("gba, must be emu\n"); fntOffset = ((u32) * (u32 *)(((const char *)GBAROM) + FNTOFFSET)); fatOffset = ((u32) * (u32 *)(((const char *)GBAROM) + FATOFFSET)); hasLoader = false; AddDevice(&nitroFSdevoptab); return (1); } } } if (ndsfile != NULL) { if ((ndsFile = fopen(ndsfile, "rb"))) { nitroSubRead(&pos, romstr, strlen(LOADERSTR)); if (strncmp(romstr, LOADERSTR, strlen(LOADERSTR)) == 0) { nitroSubSeek(&pos, LOADEROFFSET + FNTOFFSET, SEEK_SET); nitroSubRead(&pos, &fntOffset, sizeof(fntOffset)); nitroSubSeek(&pos, LOADEROFFSET + FATOFFSET, SEEK_SET); nitroSubRead(&pos, &fatOffset, sizeof(fatOffset)); fatOffset += LOADEROFFSET; fntOffset += LOADEROFFSET; hasLoader = true; } else { nitroSubSeek(&pos, FNTOFFSET, SEEK_SET); nitroSubRead(&pos, &fntOffset, sizeof(fntOffset)); nitroSubSeek(&pos, FATOFFSET, SEEK_SET); nitroSubRead(&pos, &fatOffset, sizeof(fatOffset)); hasLoader = false; } setvbuf(ndsFile, NULL, _IONBF, 0); //we dont need double buffs u_u AddDevice(&nitroFSdevoptab); return (1); } } return (0); } //Directory functs DIR_ITER *nitroFSDirOpen(struct _reent *r, DIR_ITER *dirState, const char *path) { struct nitroDIRStruct *dirStruct = (struct nitroDIRStruct *)dirState->dirStruct; //this makes it lots easier! struct stat st; char dirname[NITRONAMELENMAX]; char *cptr; char mydirpath[NITROMAXPATHLEN]; //to hold copy of path string char *dirpath = mydirpath; bool pathfound; if ((cptr = strchr(path, ':'))) path = cptr + 1; //move path past any device names (if it was nixy style wouldnt need this step >_>) strncpy(dirpath, path, sizeof(mydirpath) - 1); //copy the string (as im gonna mutalate it) dirStruct->pos = 0; if (*dirpath == '/') //if first character is '/' use absolute root path plz dirStruct->cur_dir_id = NITROROOT; //first root dir else dirStruct->cur_dir_id = chdirpathid; //else use chdirpath nitroDirReset(r, dirState); //set dir to current path do { while ((cptr = strchr(dirpath, '/')) == dirpath) { dirpath++; //move past any leading / or // together } if (cptr) *cptr = 0; //erase / if (*dirpath == 0) { //are we at the end of the path string?? if so there is nothing to search for we're already here ! pathfound = true; //mostly this handles searches for root or / or no path specified cases break; } pathfound = false; while (nitroFSDirNext(r, dirState, dirname, &st) == 0) { if ((st.st_mode == S_IFDIR) && !(strcmp(dirname, dirpath))) { //if its a directory and name matches dirpath dirStruct->cur_dir_id = dirStruct->dir_id; //move us to the next dir in tree nitroDirReset(r, dirState); //set dir to current path we just found... pathfound = true; break; } }; if (!pathfound) break; dirpath = cptr + 1; //move to right after last / we found } while (cptr); // go till after the last / if (pathfound) { return (dirState); } else { r->_errno = ENOENT; return (NULL); } } int nitroFSDirClose(struct _reent *r, DIR_ITER *dirState) { return (0); } /*Consts containing relative system path strings*/ const char *syspaths[2] = { ".", ".."}; //reset dir to start of entry selected by dirStruct->cur_dir_id which should be set in dirOpen okai?! int nitroDirReset(struct _reent *r, DIR_ITER *dirState) { struct nitroDIRStruct *dirStruct = (struct nitroDIRStruct *)dirState->dirStruct; //this makes it lots easier! struct ROM_FNTDir dirsubtable; off_t *pos = &dirStruct->pos; nitroSubSeek(pos, fntOffset + ((dirStruct->cur_dir_id & NITRODIRMASK) * sizeof(struct ROM_FNTDir)), SEEK_SET); nitroSubRead(pos, &dirsubtable, sizeof(dirsubtable)); dirStruct->namepos = dirsubtable.entry_start; //set namepos to first entry in this dir's table dirStruct->entry_id = dirsubtable.entry_file_id; //get number of first file ID in this branch dirStruct->parent_id = dirsubtable.parent_id; //save parent ID in case we wanna add ../ functionality dirStruct->spc = 0; //system path counter, first two dirnext's deliver . and .. return (0); } int nitroFSDirNext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *st) { unsigned char next; struct nitroDIRStruct *dirStruct = (struct nitroDIRStruct *)dirState->dirStruct; //this makes it lots easier! off_t *pos = &dirStruct->pos; if (dirStruct->spc <= 1) { if (st) st->st_mode = S_IFDIR; if ((dirStruct->spc == 0) || (dirStruct->cur_dir_id == NITROROOT)) { // "." or its already root (no parent) dirStruct->dir_id = dirStruct->cur_dir_id; } else { // ".." dirStruct->dir_id = dirStruct->parent_id; } strcpy(filename, syspaths[dirStruct->spc++]); return (0); } nitroSubSeek(pos, fntOffset + dirStruct->namepos, SEEK_SET); nitroSubRead(pos, &next, sizeof(next)); // next: high bit 0x80 = entry isdir.. other 7 bits r size, the 16 bits following name are dir's entryid (starts with f000) // 00 = endoftable // if (next) { if (next & NITROISDIR) { if (st) st->st_mode = S_IFDIR; next &= NITROISDIR ^ 0xff; //invert bits and mask off 0x80 nitroSubRead(pos, filename, next); nitroSubRead(&dirStruct->pos, &dirStruct->dir_id, sizeof(dirStruct->dir_id)); //read the dir_id //grr cant get the struct member size?, just wanna test it so moving on... // nitroSubRead(pos,&dirStruct->dir_id,sizeof(u16)); //read the dir_id dirStruct->namepos += next + sizeof(u16) + 1; //now we points to next one plus dir_id size:D } else { if (st) st->st_mode = 0; nitroSubRead(pos, filename, next); dirStruct->namepos += next + 1; //now we points to next one :D //read file info to get filesize (and for fileopen) nitroSubSeek(pos, fatOffset + (dirStruct->entry_id * sizeof(struct ROM_FAT)), SEEK_SET); nitroSubRead(pos, &dirStruct->romfat, sizeof(dirStruct->romfat)); //retrieve romfat entry (contains filestart and end positions) dirStruct->entry_id++; //advance ROM_FNTStrFile ptr if (st) st->st_size = dirStruct->romfat.bottom - dirStruct->romfat.top; //calculate filesize } filename[(int)next] = 0; //zero last char return (0); } else { r->_errno = EIO; return (-1); } } //fs functs int nitroFSOpen(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) { struct nitroFSStruct *fatStruct = (struct nitroFSStruct *)fileStruct; struct nitroDIRStruct dirStruct; DIR_ITER dirState; dirState.dirStruct = &dirStruct; //create a temp dirstruct struct _reent dre; struct stat st; //all these are just used for reading the dir ~_~ char dirfilename[NITROMAXPATHLEN]; // to hold a full path (i tried to avoid using so much stack but blah :/) char *filename; // to hold filename char *cptr; //used to string searching and manipulation cptr = (char *)path + strlen(path); //find the end... filename = NULL; do { if ((*cptr == '/') || (*cptr == ':')) { // split at either / or : (whichever comes first form the end!) cptr++; strncpy(dirfilename, path, cptr - path); //copy string up till and including/ or : zero rest dirfilename[cptr - path] = 0; //it seems strncpy doesnt always zero?! filename = cptr; //filename = now remainder of string break; } } while (cptr-- != path); //search till start if (!filename) { //we didnt find a / or : ? shouldnt realyl happen but if it does... filename = (char *)path; //filename = complete path dirfilename[0] = 0; //make directory path "" } if (nitroFSDirOpen(&dre, &dirState, dirfilename)) { fatStruct->start = 0; while (nitroFSDirNext(&dre, &dirState, dirfilename, &st) == 0) { if (!(st.st_mode & S_IFDIR) && (strcmp(dirfilename, filename) == 0)) { //Found the *file* youre looking for!! fatStruct->start = dirStruct.romfat.top; fatStruct->end = dirStruct.romfat.bottom; if (hasLoader) { fatStruct->start += LOADEROFFSET; fatStruct->end += LOADEROFFSET; } break; } } if (fatStruct->start) { nitroSubSeek(&fatStruct->pos, fatStruct->start, SEEK_SET); //seek to start of file return (0); //woot! } nitroFSDirClose(&dre, &dirState); } if (r->_errno == 0) { r->_errno = ENOENT; } return (-1); //teh fail } int nitroFSClose(struct _reent *r, void* fd) { return (0); } ssize_t nitroFSRead(struct _reent *r, void* fd, char *ptr, size_t len) { struct nitroFSStruct *fatStruct = (struct nitroFSStruct *)fd; off_t *npos = &fatStruct->pos; if (*npos + len > fatStruct->end) len = fatStruct->end - *npos; //dont let us read past the end plz! if (*npos > fatStruct->end) return (0); //hit eof return (nitroSubRead(npos, ptr, len)); } off_t nitroFSSeek(struct _reent *r, void* fd, off_t pos, int dir) { //need check for eof here... struct nitroFSStruct *fatStruct = (struct nitroFSStruct *)fd; off_t *npos = &fatStruct->pos; if (dir == SEEK_SET) pos += fatStruct->start; //add start from .nds file offset else if (dir == SEEK_END) pos += fatStruct->end; //set start to end of file (useless?) if (pos > fatStruct->end) return (-1); //dont let us read past the end plz! nitroSubSeek(npos, pos, dir); return (*npos - fatStruct->start); } int nitroFSFstat(struct _reent *r, void* fd, struct stat *st) { struct nitroFSStruct *fatStruct = (struct nitroFSStruct *)fd; st->st_size = fatStruct->end - fatStruct->start; return (0); } int nitroFSstat(struct _reent *r, const char *file, struct stat *st) { struct nitroFSStruct fatStruct; struct nitroDIRStruct dirStruct; DIR_ITER dirState; if (nitroFSOpen(NULL, &fatStruct, file, 0, 0) >= 0) { st->st_mode = S_IFREG; st->st_size = fatStruct.end - fatStruct.start; return (0); } dirState.dirStruct = &dirStruct; if ((nitroFSDirOpen(r, &dirState, file) != NULL)) { st->st_mode = S_IFDIR; nitroFSDirClose(r, &dirState); return (0); } r->_errno = ENOENT; return (-1); } int nitroFSChdir(struct _reent *r, const char *name) { struct nitroDIRStruct dirStruct; DIR_ITER dirState; dirState.dirStruct = &dirStruct; if ((name != NULL) && (nitroFSDirOpen(r, &dirState, name) != NULL)) { chdirpathid = dirStruct.cur_dir_id; nitroFSDirClose(r, &dirState); return (0); } else { r->_errno = ENOENT; return (-1); } }