/*---------------------------------------------------------------------------* Project: NintendoWare File: lyt_Arc.cpp Copyright (C)2009-2011 Nintendo/HAL Laboratory, Inc. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo and/or its licensed developers and are protected by national and international copyright laws. 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. The content herein is highly confidential and should be handled accordingly. $Revision: 31311 $ *---------------------------------------------------------------------------*/ #include "precompiled.h" #include #include // for tolower namespace nw { namespace lyt { 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 ) namespace { inline wchar_t* GetStringPtr( wchar_t* str, size_t offset ) { return reinterpret_cast( reinterpret_cast(str) + offset ); } inline const wchar_t* GetStringPtr( const wchar_t* str, size_t offset ) { return reinterpret_cast( reinterpret_cast(str) + offset ); } #if defined(_MSC_VER) && _MSC_VER >= 1500 #pragma warning(push) #pragma warning(disable: 4996) #endif inline size_t WcsToMbs( char* mbstr, const wchar_t* wcstr, size_t count ) { return std::wcstombs(mbstr, wcstr, count); } #if defined(_MSC_VER) && _MSC_VER >= 1500 #pragma warning(pop) #endif } // namespace bool ARCInitHandle(void* arcStart, ARCHandle* handle) { FSTEntry* FSTEntries; ARCHeader* arcHeader; arcHeader = (ARCHeader*)arcStart; NN_ASSERTMSG(arcHeader->signature == DARCH_SIGNATURE, "ARCInitHandle: bad archive format"); NN_ASSERTMSG(arcHeader->byteOrder == DARCH_BYTE_ORDER_MARK, "ARCInitHandle: bad archive format"); NN_ASSERTMSG(arcHeader->version == DARCH_VERSION, "ARCInitHandle: bad archive format"); handle->archiveStartAddr = arcStart; handle->FSTStart = FSTEntries = reinterpret_cast((u32)arcStart + arcHeader->fstStart); handle->fileStart = (void*)((u32)arcStart + arcHeader->fileStart); NN_ASSERTMSG(FSTEntries != NULL, "ARCInitHandle: bad archive format"); handle->entryNum = nextDir(FSTEntries, 0); handle->FSTStringStart = reinterpret_cast(&(FSTEntries[handle->entryNum])); handle->FSTLength = (u32)arcHeader->fstSize; handle->currDir = 0; return true; } bool ARCOpen(ARCHandle* handle, const wchar_t* fileName, ARCFileInfo* af) { s32 entry; FSTEntry* FSTEntries; NN_ASSERTMSG( handle, "ARCOpen(): NULL pointer is specified to ARCHandle structure" ); NN_ASSERTMSG( fileName, "ARCOpen(): NULL pointer is specified to fileName" ); NN_ASSERTMSG( af, "ARCOpen(): NULL pointer is specified to ARCFileInfo structure" ); FSTEntries = (FSTEntry*)handle->FSTStart; entry = ARCConvertPathToEntrynum(handle, fileName); #if defined(NW_DEBUG) if (0 > entry) { const size_t BufMax = 127; wchar_t currentDir[BufMax + 1]; char chBuf[BufMax + 1]; WcsToMbs(chBuf, fileName, BufMax); chBuf[BufMax] = '\0'; NN_LOG("Warning: ARCOpen(): file '%s' was not found", chBuf); ARCGetCurrentDir(handle, currentDir, BufMax + 1); WcsToMbs(chBuf, currentDir, BufMax); chBuf[BufMax] = '\0'; NN_LOG(" under %s in the archive.\n", chBuf); NN_ASSERT(false); } #endif NN_ASSERTMSG( !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; NN_ASSERTMSG(handle, "ARCFastOpen(): null pointer is specified to ARCHandle address "); NN_ASSERTMSG(af, "ARCFastOpen(): null pointer is specified to ARCFileInfo address "); NN_ASSERTMSG((0 <= entrynum) && (entrynum < static_cast(handle->entryNum)), "ARCFastOpen(): specified entry number '%d' is out of range ", entrynum); FSTEntries = (FSTEntry*)handle->FSTStart; NN_ASSERTMSG(!entryIsDir(FSTEntries, entrynum), "ARCFastOpen(): entry number '%d' is assigned to a directory ", entrynum); if ( (entrynum < 0) || (entrynum >= static_cast(handle->entryNum)) || entryIsDir(FSTEntries, entrynum) ) { return false; } af->handle = handle; af->startOffset = filePosition(FSTEntries, entrynum); af->length = fileLength(FSTEntries, entrynum); return true; } /*---------------------------------------------------------------------------* 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 wchar_t* path, const wchar_t* 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 wchar_t* pathPtr) { const wchar_t* ptr; wchar_t* stringPtr; bool isDir; s32 length; // must be signed u32 dirLookAt; u32 i; const wchar_t* origPathPtr = pathPtr; FSTEntry* FSTEntries; NN_ASSERTMSG(handle, "ARCConvertPathToEntrynum(): null pointer is specified to ARCHandle structure"); NN_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 = GetStringPtr(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 } bool ARCEntrynumIsDir( const ARCHandle * handle, s32 entrynum ) { FSTEntry* FSTEntries; NN_ASSERTMSG(handle, "ARCEntrynumIsDir(): null pointer is specified to ARCHandle structure"); NN_ASSERTMSG((entrynum >= 0) , "ARCEntrynumIsDir(): no file/directory is specified to entrynum"); FSTEntries = (FSTEntry*)handle->FSTStart; return entryIsDir( FSTEntries, entrynum ); } /*---------------------------------------------------------------------------* 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(wchar_t* dest, wchar_t* 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, wchar_t* path, u32 maxlen) { wchar_t* name; u32 loc; FSTEntry* FSTEntries; FSTEntries = (FSTEntry*)handle->FSTStart; if (entry == 0) { return 0; } name = GetStringPtr(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, wchar_t* path, u32 maxlen) { u32 loc; FSTEntry* FSTEntries; FSTEntries = (FSTEntry*)handle->FSTStart; NN_ASSERTMSG((0 <= entrynum) && (entrynum < static_cast(handle->entryNum)), "ARCConvertEntrynumToPath: specified entrynum(%d) is out of range ", entrynum ); NN_ASSERTMSG(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. NN_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, wchar_t* path, u32 maxlen) { NN_ASSERTMSG( 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; NN_ASSERTMSG(handle, "ARCGetFileAddr(): af->handle is null pointer. Maybe it's not initialized properly"); NN_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) { NN_UNUSED_VAR(af); return true; } bool ARCChangeDir(ARCHandle* handle, const wchar_t* dirName) { s32 entry; FSTEntry* FSTEntries; NN_ASSERTMSG(handle, "ARCChangeDir(): null pointer is specified to ARCHandle"); NN_ASSERTMSG(dirName, "ARCChangeDir(): null pointer is specified to dirname"); entry = ARCConvertPathToEntrynum(handle, dirName); FSTEntries = (FSTEntry*)handle->FSTStart; #if defined(NW_DEBUG) if (0 > entry) { const size_t BufMax = 127; wchar_t currentDir[BufMax + 1]; char chBuf[BufMax + 1]; WcsToMbs(chBuf, dirName, BufMax); chBuf[BufMax] = '\0'; NN_LOG("ARCOpendir(): directory '%s' is not found", chBuf); ARCGetCurrentDir(handle, currentDir, BufMax + 1); WcsToMbs(chBuf, currentDir, BufMax); chBuf[BufMax] = '\0'; NN_LOG(" under %s ", chBuf); NN_ASSERT(false); } #endif NN_ASSERTMSG(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 ARCChangeDir(ARCHandle* handle, s32 entrynum) { NN_ASSERTMSG( handle != NULL, "ARCChangeDir(): null pointer is specified to ARCHandle address"); NN_ASSERTMSG( (0 <= entrynum) && (entrynum < static_cast(handle->entryNum)), "ARCChangeDir(): specified entry number '%d' is out of range", entrynum); FSTEntry* FSTEntries = (FSTEntry*)handle->FSTStart; NN_ASSERTMSG( entryIsDir(FSTEntries, entrynum), "ARCChangeDir(): %s entry[%d] a regular file", entrynum); if (handle == NULL) { return false; } if (entrynum < 0 || static_cast(handle->entryNum) <= entrynum) { return false; } if (!entryIsDir(FSTEntries, entrynum)) { return false; } handle->currDir = (u32)entrynum; return true; } bool ARCOpenDir(ARCHandle* handle, const wchar_t* dirName, ARCDir* dir) { s32 entry; FSTEntry* FSTEntries; NN_ASSERTMSG(handle, "ARCOpenDir(): null pointer is specified to ARCHandle"); NN_ASSERTMSG(dirName, "ARCOpenDir(): null pointer is specified to ARCDir"); entry = ARCConvertPathToEntrynum(handle, dirName); FSTEntries = (FSTEntry*)handle->FSTStart; #if defined(NW_DEBUG) if (entry < 0) { const size_t BufMax = 127; wchar_t currentDir[BufMax + 1]; char chBuf[BufMax + 1]; WcsToMbs(chBuf, dirName, BufMax); chBuf[BufMax] = '\0'; NN_LOG("ARCOpenDir(): directory '%s' is not found", chBuf); ARCGetCurrentDir(handle, currentDir, BufMax + 1); WcsToMbs(chBuf, currentDir, BufMax); chBuf[BufMax] = '\0'; NN_LOG(" under %s ", chBuf); NN_ASSERT(false); } #endif NN_ASSERTMSG( 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 ARCOpenDir(ARCHandle* handle, s32 entrynum, ARCDir* dir) { NN_ASSERTMSG( handle != NULL, "ARCOpenDir(): null pointer is specified to ARCHandle address"); NN_ASSERTMSG( (0 <= entrynum) && (entrynum < static_cast(handle->entryNum)), "ARCOpenDir(): specified entry number '%d' is out of range", entrynum); FSTEntry* FSTEntries = (FSTEntry*)handle->FSTStart; NN_ASSERTMSG( entryIsDir(FSTEntries, entrynum), "ARCOpenDir(): %s entry[%d] a regular file", entrynum); if (handle == NULL) { return false; } if (entrynum < 0 || static_cast(handle->entryNum) <= entrynum) { return false; } if (!entryIsDir(FSTEntries, entrynum)) { return false; } dir->handle = handle; dir->entryNum = (u32)entrynum; dir->location = (u32)entrynum + 1; dir->next = nextDir(FSTEntries, entrynum); return true; } bool ARCReadDir(ARCDir* dir, ARCDirEntry* dirent) { u32 loc; FSTEntry* FSTEntries; ARCHandle* handle; handle = dir->handle; NN_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 = GetStringPtr(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) { NN_UNUSED_VAR(dir); return true; } } // namespace lyt } // namespace nw