/*---------------------------------------------------------------------------* Project: Revolution archiver library File: arc.c Copyright (C)2001-2006 Nintendo All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: arc.c,v $ Revision 1.4 07/04/2006 09:08:44 hiratsu Added "const" to arguments. Revision 1.3 05/16/2006 02:05:24 kawaset Supported archive files created by "darch[D] -c ." Revision 1.2 04/19/2006 10:45:39 hiratsu Modified include path for Revolution. Revision 1.1 01/06/2006 02:31:04 yasuh-to initial check in 1 7/02/01 11:42p Hashida Initial revision. $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include // for tolower #include typedef struct FSTEntry FSTEntry; struct FSTEntry { unsigned int isDirAndStringOff; // the first byte is for isDir // the next 3bytes: name offset unsigned int parentOrPosition; // parent entry (dir entry) // position (file entry) unsigned int nextEntryOrLength; // next entry (dir entry) // length (file entry) }; #define entryIsDir(fstStart, i) \ ( ( ( fstStart[i].isDirAndStringOff & 0xff000000 ) == 0 )? FALSE:TRUE ) #define stringOff(fstStart, i) \ ( fstStart[i].isDirAndStringOff & 0x00ffffff ) #define parentDir(fstStart, i) \ ( fstStart[i].parentOrPosition ) #define nextDir(fstStart, i) \ ( fstStart[i].nextEntryOrLength ) #define filePosition(fstStart, i) \ ( fstStart[i].parentOrPosition ) #define fileLength(fstStart, i) \ ( fstStart[i].nextEntryOrLength ) BOOL ARCInitHandle(void* arcStart, ARCHandle* handle) { FSTEntry* FSTEntries; ARCHeader* arcHeader; arcHeader = (ARCHeader*)arcStart; if (arcHeader->magic != DARCH_MAGIC) { OSHalt("ARCInitHandle: bad archive format"); } handle->archiveStartAddr = arcStart; handle->FSTStart = FSTEntries = (void*)((u32)arcStart + arcHeader->fstStart); handle->fileStart = (void*)((u32)arcStart + arcHeader->fileStart); ASSERTMSG(FSTEntries != NULL, "ARCInitHandle: bad archive format"); handle->entryNum = nextDir(FSTEntries, 0); handle->FSTStringStart = (char*)&(FSTEntries[handle->entryNum]); handle->FSTLength = (u32)arcHeader->fstSize; handle->currDir = 0; return TRUE; } BOOL ARCOpen(ARCHandle* handle, const char* fileName, ARCFileInfo* af) { s32 entry; char currentDir[128]; FSTEntry* FSTEntries; ASSERTMSG( handle, "ARCOpen(): NULL pointer is specified to ARCHandle structure" ); ASSERTMSG( fileName, "ARCOpen(): NULL pointer is specified to fileName" ); ASSERTMSG( af, "ARCOpen(): NULL pointer is specified to ARCFileInfo structure" ); FSTEntries = (FSTEntry*)handle->FSTStart; entry = ARCConvertPathToEntrynum(handle, fileName); if (0 > entry) { #if 1 ARCGetCurrentDir(handle, currentDir, 128); #endif OSReport("Warning: ARCOpen(): file '%s' was not found under %s in the archive.\n", fileName, currentDir); return FALSE; } ASSERTMSG1( !entryIsDir(FSTEntries, entry), "ARCOpen(): %s is a directory", fileName ); if ( (entry < 0) || entryIsDir(FSTEntries, entry) ) { return FALSE; } af->handle = handle; af->startOffset = filePosition(FSTEntries, entry); af->length = fileLength(FSTEntries, entry); return TRUE; } BOOL ARCFastOpen(ARCHandle* handle, s32 entrynum, ARCFileInfo* af) { FSTEntry* FSTEntries; ASSERTMSG(handle, "ARCFastOpen(): null pointer is specified to ARCHandle address "); ASSERTMSG(af, "ARCFastOpen(): null pointer is specified to ARCFileInfo address "); ASSERTMSG1((0 <= entrynum) && (entrynum < handle->entryNum), "ARCFastOpen(): specified entry number '%d' is out of range ", entrynum); FSTEntries = (FSTEntry*)handle->FSTStart; ASSERTMSG1(!entryIsDir(FSTEntries, entrynum), "ARCFastOpen(): entry number '%d' is assigned to a directory ", entrynum); if ( (entrynum < 0) || (entrynum >= handle->entryNum) || entryIsDir(FSTEntries, entrynum) ) { return FALSE; } af->handle = handle; af->startOffset = filePosition(FSTEntries, entrynum); af->length = fileLength(FSTEntries, entrynum); return TRUE; } /*---------------------------------------------------------------------------* Name: isSame Description: compare two strings up to the first string hits '/' or '\0' Arguments: path path name string directory or file name Returns: TRUE if same, FALSE if not *---------------------------------------------------------------------------*/ static BOOL isSame(const char* path, const char* string) { while(*string != '\0') { // compare in case-insensitive if (tolower(*path++) != tolower(*string++)) { return FALSE; } } if ( (*path == '/') || (*path == '\0') ) { return TRUE; } return FALSE; } s32 ARCConvertPathToEntrynum(ARCHandle* handle, const char* pathPtr) { const char* ptr; char* stringPtr; BOOL isDir; s32 length; // must be signed u32 dirLookAt; u32 i; const char* origPathPtr = pathPtr; FSTEntry* FSTEntries; ASSERTMSG(handle, "ARCConvertPathToEntrynum(): null pointer is specified to ARCHandle structure"); ASSERTMSG(pathPtr, "ARCConvertPathToEntrynum(): null pointer is specified to file name"); dirLookAt = handle->currDir; FSTEntries = (FSTEntry*)handle->FSTStart; while (1) { // check /, ./, ../ if (*pathPtr == '\0') { return (s32)dirLookAt; } else if (*pathPtr == '/') { dirLookAt = 0; pathPtr++; continue; } else if (*pathPtr == '.') { if (*(pathPtr + 1) == '.') { if (*(pathPtr + 2) == '/') { dirLookAt = parentDir(FSTEntries, dirLookAt); pathPtr += 3; continue; } else if (*(pathPtr + 2) == '\0') { return (s32)parentDir(FSTEntries, dirLookAt); } } else if (*(pathPtr + 1) == '/') { pathPtr += 2; continue; } else if (*(pathPtr + 1) == '\0') { return (s32)dirLookAt; } } // directory or file? Can be checked by the endmark for(ptr = pathPtr; (*ptr != '\0') && (*ptr != '/'); ptr++) ; // it's true that one ends with '/' should be a directory name, // but one ends with '\0' is not always a file name. isDir = (*ptr == '\0')? FALSE : TRUE; length = (s32)(ptr - pathPtr); ptr = pathPtr; for(i = dirLookAt + 1; i < nextDir(FSTEntries, dirLookAt); i = entryIsDir(FSTEntries, i)? nextDir(FSTEntries, i): (i+1) ) { dot: // isDir == FALSE doesn't mean it's a file. // so it's legal to compare it to a directory if ( ( entryIsDir(FSTEntries, i) == FALSE ) && (isDir == TRUE) ) { continue; } stringPtr = handle->FSTStringStart + stringOff(FSTEntries, i); // check "." // support archive files created by "darch[D] -c ." if (*stringPtr == '.' && *(stringPtr + 1) == '\0') { i++; goto dot; } if ( isSame(ptr, stringPtr) == TRUE ) { goto next_hier; } } // for() return -1; next_hier: if (! isDir) { return (s32)i; } dirLookAt = i; pathPtr += length + 1; } // while(1) // NOT REACHED } /*---------------------------------------------------------------------------* Name: myStrncpy Description: copy a string. Difference from standard strncpy is: 1. do not pad dest with null characters 2. do not terminate dest with a null 3. return the number of chars copied Arguments: dest destination src source maxlen maximum length of copy Returns: Num of chars copied *---------------------------------------------------------------------------*/ static u32 myStrncpy(char* dest, char* src, u32 maxlen) { u32 i = maxlen; while( (i > 0) && (*src != 0) ) { *dest++ = *src++; i--; } return (maxlen - i); } /*---------------------------------------------------------------------------* Name: entryToPath Description: Convert from fst entry to path. The result is not null terminated. Arguments: entry FST entry path pointer to store the result maxlen size of the buffer start from path Returns: Num of chars copied *---------------------------------------------------------------------------*/ static u32 entryToPath(ARCHandle* handle, u32 entry, char* path, u32 maxlen) { char* name; u32 loc; FSTEntry* FSTEntries; FSTEntries = (FSTEntry*)handle->FSTStart; if (entry == 0) { return 0; } name = handle->FSTStringStart + stringOff(FSTEntries, entry); loc = entryToPath(handle, parentDir(FSTEntries, entry), path, maxlen); if (loc == maxlen) { return loc; } *(path + loc++) = '/'; loc += myStrncpy(path + loc, name, maxlen - loc); return loc; } /*---------------------------------------------------------------------------* Name: ARCConvertEntrynumToPath Description: Convert from fst entry to path. Arguments: entrynum FST entry path pointer to store the result maxlen size of the buffer start from path Returns: TRUE if all the path fits in maxlen. FALSE if not (in this case, it's truncated to fit maxlen). *---------------------------------------------------------------------------*/ static BOOL ARCConvertEntrynumToPath(ARCHandle* handle, s32 entrynum, char* path, u32 maxlen) { u32 loc; FSTEntry* FSTEntries; FSTEntries = (FSTEntry*)handle->FSTStart; ASSERTMSG1((0 <= entrynum) && (entrynum < handle->entryNum), "ARCConvertEntrynumToPath: specified entrynum(%d) is out of range ", entrynum ); ASSERTMSG1(1 < maxlen, "ARCConvertEntrynumToPath: maxlen should be more than 1 (%d is specified)", maxlen ); // currently only directories can be converted to path // finding the directory where a file is located is not so easy // while finding the parent directory of a directory is a piece of cake. ASSERTMSG(entryIsDir(FSTEntries, entrynum), "ARCConvertEntrynumToPath: cannot convert an entry num for a file to path "); loc = entryToPath(handle, (u32)entrynum, path, maxlen); if (loc == maxlen) { // Overwrite the last char with NULL path[maxlen - 1] = '\0'; return FALSE; } // For directories, put '/' at the last if (entryIsDir(FSTEntries, entrynum)) { if (loc == maxlen - 1) { // There's no room to put the last '/', so just put '\0' and return FALSE path[loc] = '\0'; return FALSE; } path[loc++] = '/'; } path[loc] = '\0'; return TRUE; } /*---------------------------------------------------------------------------* Name: ARCGetCurrentDir Description: Get current directory Arguments: path pointer to store the result maxlen size of the buffer start from path Returns: TRUE if all the path fits in maxlen. FALSE if not (in this case, it's truncated to fit maxlen). *---------------------------------------------------------------------------*/ BOOL ARCGetCurrentDir(ARCHandle* handle, char* path, u32 maxlen) { ASSERTMSG1( 1 < maxlen, "ARCGetCurrentDir: maxlen should be more than 1 (%d is specified)", maxlen ); return ARCConvertEntrynumToPath(handle, (s32)handle->currDir, path, maxlen); } void* ARCGetStartAddrInMem(ARCFileInfo* af) { ARCHandle* handle; handle = af->handle; ASSERTMSG(handle, "ARCGetFileAddr(): af->handle is null pointer. Maybe it's not initialized properly"); ASSERTMSG(af, "ARCGetFileAddr(): null pointer is specified to ARCFileInfo structure"); return (void*)( (u32)handle->archiveStartAddr + af->startOffset ); } u32 ARCGetStartOffset(ARCFileInfo* af) { return af->startOffset; } u32 ARCGetLength(ARCFileInfo* af) { return af->length; } BOOL ARCClose(ARCFileInfo* af) { #pragma unused(af) return TRUE; } BOOL ARCChangeDir(ARCHandle* handle, const char* dirName) { s32 entry; #ifdef _DEBUG char currentDir[128]; #endif FSTEntry* FSTEntries; ASSERTMSG(handle, "ARCChangeDir(): null pointer is specified to ARCHandle"); ASSERTMSG(dirName, "ARCChangeDir(): null pointer is specified to dirname"); entry = ARCConvertPathToEntrynum(handle, dirName); FSTEntries = (FSTEntry*)handle->FSTStart; #ifdef _DEBUG if (0 > entry) { ARCGetCurrentDir(handle, currentDir, 128); OSPanic(__FILE__, __LINE__, "ARCOpendir(): directory '%s' is not found under %s ", dirName, currentDir); } #endif ASSERTMSG1(entryIsDir(FSTEntries, entry), "ARCChangeDir(): %s is not a directory", dirName ); if ( (entry < 0) || (entryIsDir(FSTEntries, entry) == FALSE) ) { return FALSE; } handle->currDir = (u32)entry; return TRUE; } BOOL ARCOpenDir(ARCHandle* handle, const char* dirName, ARCDir* dir) { s32 entry; FSTEntry* FSTEntries; #ifdef _DEBUG char currentDir[128]; #endif ASSERTMSG(handle, "ARCOpenDir(): null pointer is specified to ARCHandle"); ASSERTMSG(dirName, "ARCOpenDir(): null pointer is specified to ARCDir"); entry = ARCConvertPathToEntrynum(handle, dirName); FSTEntries = (FSTEntry*)handle->FSTStart; #ifdef _DEBUG if (entry < 0) { ARCGetCurrentDir(handle, currentDir, 128); OSPanic(__FILE__, __LINE__, "ARCOpendir(): directory '%s' is not found under %s ", dirName, currentDir); } #endif ASSERTMSG1( entryIsDir(FSTEntries, entry), "ARCOpenDir(): %s is a regular file", dirName ); if ( (entry < 0) || (entryIsDir(FSTEntries, entry) == FALSE) ) return FALSE; dir->handle = handle; dir->entryNum = (u32)entry; dir->location = (u32)entry + 1; dir->next = nextDir(FSTEntries, entry); return TRUE; } BOOL ARCReadDir(ARCDir* dir, ARCDirEntry* dirent) { u32 loc; FSTEntry* FSTEntries; ARCHandle* handle; handle = dir->handle; ASSERTMSG(handle, "ARCReadDir: dir->handle is null pointer. Maybe it's not initialized properly"); FSTEntries = (FSTEntry*)handle->FSTStart; // Check the next location. loc == dir->next means it reached the // end of the directory. Other check is for illegal setting by ARCSeekDir. loc = dir->location; retry: if ( (loc <= dir->entryNum) || (dir->next <= loc) ) return FALSE; dirent->handle = handle; dirent->entryNum = loc; dirent->isDir = entryIsDir(FSTEntries, loc); dirent->name = handle->FSTStringStart + stringOff(FSTEntries, loc); // check "." // support archive files created by "darch[D] -c ." if (dirent->name[0] == '.' && dirent->name[1] == '\0') { loc++; goto retry; } dir->location = entryIsDir(FSTEntries, loc)? nextDir(FSTEntries, loc) : (loc+1); return TRUE; } BOOL ARCCloseDir(ARCDir* dir) { #pragma unused (dir) return TRUE; }