1 /*---------------------------------------------------------------------------*
2   Project:  Revolution archiver library
3   File:     arc.c
4 
5   Copyright (C) 2001-2006 Nintendo All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law.  They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Log: arc.c,v $
14   Revision 1.5  2006/09/29 08:07:34  shirakae
15   Added ARCEntrynumIsDir
16 
17   Revision 1.4  2006/07/04 09:08:44  hiratsu
18   Added "const" to arguments.
19 
20   Revision 1.3  2006/05/16 02:05:24  kawaset
21   Supported archive files created by "darch[D] -c ."
22 
23   Revision 1.2  2006/04/19 10:45:39  hiratsu
24   Modified include path for Revolution.
25 
26   Revision 1.1  2006/01/06 02:31:04  yasuh-to
27   initial check in
28 
29 
30     1     7/02/01 11:42p Hashida
31     Initial revision.
32 
33   $NoKeywords: $
34  *---------------------------------------------------------------------------*/
35 
36 #include <revolution/os.h>
37 #include <ctype.h>          // for tolower
38 #include <revolution/arc.h>
39 
40 typedef struct FSTEntry FSTEntry;
41 
42 struct FSTEntry
43 {
44     unsigned int    isDirAndStringOff;    // the first byte is for isDir
45                                           // the next 3bytes: name offset
46     unsigned int    parentOrPosition;     // parent entry (dir entry)
47                                           // position (file entry)
48     unsigned int    nextEntryOrLength;    // next entry (dir entry)
49                                           // length (file entry)
50 };
51 
52 #define entryIsDir(fstStart, i)     \
53     ( ( ( fstStart[i].isDirAndStringOff & 0xff000000 ) == 0 )? FALSE:TRUE )
54 #define stringOff(fstStart, i)      \
55         ( fstStart[i].isDirAndStringOff & 0x00ffffff )
56 #define parentDir(fstStart, i)       \
57         ( fstStart[i].parentOrPosition )
58 #define nextDir(fstStart, i)        \
59         ( fstStart[i].nextEntryOrLength )
60 #define filePosition(fstStart, i)       \
61         ( fstStart[i].parentOrPosition )
62 #define fileLength(fstStart, i)         \
63         ( fstStart[i].nextEntryOrLength )
64 
ARCInitHandle(void * arcStart,ARCHandle * handle)65 BOOL ARCInitHandle(void* arcStart, ARCHandle* handle)
66 {
67     FSTEntry*           FSTEntries;
68     ARCHeader*          arcHeader;
69 
70     arcHeader = (ARCHeader*)arcStart;
71 
72     if (arcHeader->magic != DARCH_MAGIC)
73     {
74         OSHalt("ARCInitHandle: bad archive format");
75     }
76 
77     handle->archiveStartAddr = arcStart;
78     handle->FSTStart = FSTEntries = (void*)((u32)arcStart + arcHeader->fstStart);
79     handle->fileStart = (void*)((u32)arcStart + arcHeader->fileStart);
80 
81     ASSERTMSG(FSTEntries != NULL, "ARCInitHandle: bad archive format");
82 
83     handle->entryNum = nextDir(FSTEntries, 0);
84     handle->FSTStringStart = (char*)&(FSTEntries[handle->entryNum]);
85     handle->FSTLength = (u32)arcHeader->fstSize;
86     handle->currDir = 0;
87 
88     return TRUE;
89 }
90 
ARCOpen(ARCHandle * handle,const char * fileName,ARCFileInfo * af)91 BOOL ARCOpen(ARCHandle* handle, const char* fileName, ARCFileInfo* af)
92 {
93     s32         entry;
94     char        currentDir[128];
95     FSTEntry*   FSTEntries;
96 
97     ASSERTMSG( handle, "ARCOpen(): NULL pointer is specified to ARCHandle structure" );
98     ASSERTMSG( fileName, "ARCOpen(): NULL pointer is specified to fileName" );
99     ASSERTMSG( af, "ARCOpen(): NULL pointer is specified to ARCFileInfo structure" );
100 
101     FSTEntries = (FSTEntry*)handle->FSTStart;
102 
103     entry = ARCConvertPathToEntrynum(handle, fileName);
104 
105     if (0 > entry)
106     {
107 #if 1
108         ARCGetCurrentDir(handle, currentDir, 128);
109 #endif
110         OSReport("Warning: ARCOpen(): file '%s' was not found under %s in the archive.\n",
111                  fileName, currentDir);
112         return FALSE;
113     }
114 
115     ASSERTMSG1( !entryIsDir(FSTEntries, entry), "ARCOpen(): %s is a directory", fileName );
116 
117     if ( (entry < 0) || entryIsDir(FSTEntries, entry) )
118     {
119         return FALSE;
120     }
121 
122     af->handle      = handle;
123     af->startOffset = filePosition(FSTEntries, entry);
124     af->length      = fileLength(FSTEntries, entry);
125 
126     return TRUE;
127 }
128 
ARCFastOpen(ARCHandle * handle,s32 entrynum,ARCFileInfo * af)129 BOOL ARCFastOpen(ARCHandle* handle, s32 entrynum, ARCFileInfo* af)
130 {
131     FSTEntry*           FSTEntries;
132 
133     ASSERTMSG(handle, "ARCFastOpen(): null pointer is specified to ARCHandle address  ");
134     ASSERTMSG(af,
135               "ARCFastOpen(): null pointer is specified to ARCFileInfo address  ");
136     ASSERTMSG1((0 <= entrynum) && (entrynum < handle->entryNum),
137                "ARCFastOpen(): specified entry number '%d' is out of range  ",
138                entrynum);
139 
140     FSTEntries = (FSTEntry*)handle->FSTStart;
141 
142     ASSERTMSG1(!entryIsDir(FSTEntries, entrynum),
143                "ARCFastOpen(): entry number '%d' is assigned to a directory  ",
144                entrynum);
145 
146     if ( (entrynum < 0) || (entrynum >= handle->entryNum) ||
147          entryIsDir(FSTEntries, entrynum) )
148     {
149         return FALSE;
150     }
151 
152     af->handle      = handle;
153     af->startOffset = filePosition(FSTEntries, entrynum);
154     af->length      = fileLength(FSTEntries, entrynum);
155 
156     return TRUE;
157 }
158 
159  /*---------------------------------------------------------------------------*
160     Name:               isSame
161 
162     Description:        compare two strings up to the first string hits '/' or
163                         '\0'
164 
165     Arguments:          path     path name
166                         string   directory or file name
167 
168     Returns:            TRUE if same, FALSE if not
169  *---------------------------------------------------------------------------*/
isSame(const char * path,const char * string)170 static BOOL isSame(const char* path, const char* string)
171 {
172     while(*string != '\0')
173     {
174         // compare in case-insensitive
175         if (tolower(*path++) != tolower(*string++))
176         {
177             return FALSE;
178         }
179     }
180 
181     if ( (*path == '/') || (*path == '\0') )
182     {
183         return TRUE;
184     }
185 
186     return FALSE;
187 }
188 
189 
ARCConvertPathToEntrynum(ARCHandle * handle,const char * pathPtr)190 s32 ARCConvertPathToEntrynum(ARCHandle* handle, const char* pathPtr)
191 {
192     const char*  ptr;
193     char*        stringPtr;
194     BOOL         isDir;
195     s32          length; // must be signed
196     u32          dirLookAt;
197     u32          i;
198     const char*  origPathPtr = pathPtr;
199     FSTEntry*    FSTEntries;
200 
201 
202     ASSERTMSG(handle, "ARCConvertPathToEntrynum(): null pointer is specified to ARCHandle structure");
203     ASSERTMSG(pathPtr, "ARCConvertPathToEntrynum(): null pointer is specified to file name");
204 
205 
206     dirLookAt = handle->currDir;
207     FSTEntries = (FSTEntry*)handle->FSTStart;
208 
209     while (1)
210     {
211 
212         // check /, ./, ../
213         if (*pathPtr == '\0')
214         {
215             return (s32)dirLookAt;
216         } else if (*pathPtr == '/')
217         {
218             dirLookAt = 0;
219             pathPtr++;
220             continue;
221         }
222         else if (*pathPtr == '.')
223         {
224             if (*(pathPtr + 1) == '.')
225             {
226                 if (*(pathPtr + 2) == '/')
227                 {
228                     dirLookAt = parentDir(FSTEntries, dirLookAt);
229                     pathPtr += 3;
230                     continue;
231                 }
232                 else if (*(pathPtr + 2) == '\0')
233                 {
234                     return (s32)parentDir(FSTEntries, dirLookAt);
235                 }
236             }
237             else if (*(pathPtr + 1) == '/')
238             {
239                 pathPtr += 2;
240                 continue;
241             }
242             else if (*(pathPtr + 1) == '\0')
243             {
244                 return (s32)dirLookAt;
245             }
246         }
247 
248         // directory or file? Can be checked by the endmark
249         for(ptr = pathPtr; (*ptr != '\0') && (*ptr != '/'); ptr++)
250             ;
251 
252         // it's true that one ends with '/' should be a directory name,
253         // but one ends with '\0' is not always a file name.
254         isDir = (*ptr == '\0')? FALSE : TRUE;
255         length = (s32)(ptr - pathPtr);
256 
257         ptr = pathPtr;
258 
259         for(i = dirLookAt + 1; i < nextDir(FSTEntries, dirLookAt);
260             i = entryIsDir(FSTEntries, i)? nextDir(FSTEntries, i): (i+1) )
261         {
262 dot:
263             // isDir == FALSE doesn't mean it's a file.
264             // so it's legal to compare it to a directory
265             if ( ( entryIsDir(FSTEntries, i) == FALSE ) &&
266                  (isDir == TRUE) )
267             {
268                 continue;
269             }
270 
271             stringPtr = handle->FSTStringStart + stringOff(FSTEntries, i);
272 
273             // check "."
274             // support archive files created by "darch[D] -c ."
275             if (*stringPtr == '.' && *(stringPtr + 1) == '\0') {
276                 i++;
277                 goto dot;
278             }
279 
280             if ( isSame(ptr, stringPtr) == TRUE )
281             {
282                 goto next_hier;
283             }
284 
285         } // for()
286 
287         return -1;
288 
289       next_hier:
290         if (! isDir)
291         {
292             return (s32)i;
293         }
294 
295         dirLookAt = i;
296         pathPtr += length + 1;
297 
298     } // while (1)
299 
300     // NOT REACHED
301 }
302 
ARCEntrynumIsDir(const ARCHandle * handle,s32 entrynum)303 BOOL  ARCEntrynumIsDir( const ARCHandle * handle, s32 entrynum )
304 {
305     FSTEntry*           FSTEntries;
306 
307     ASSERTMSG(handle, "ARCEntrynumIsDir(): null pointer is specified to ARCHandle structure");
308     ASSERTMSG((entrynum >= 0) , "ARCEntrynumIsDir(): no file/directory is specified to entrynum");
309     FSTEntries = (FSTEntry*)handle->FSTStart;
310 
311     return entryIsDir( FSTEntries, entrynum );
312 }
313 
314 /*---------------------------------------------------------------------------*
315   Name:         myStrncpy
316 
317   Description:  copy a string. Difference from standard strncpy is:
318                     1. do not pad dest with null characters
319                     2. do not terminate dest with a null
320                     3. return the number of chars copied
321 
322   Arguments:    dest        destination
323                 src         source
324                 maxlen      maximum length of copy
325 
326   Returns:      Num of chars copied
327  *---------------------------------------------------------------------------*/
myStrncpy(char * dest,char * src,u32 maxlen)328 static u32 myStrncpy(char* dest, char* src, u32 maxlen)
329 {
330     u32         i = maxlen;
331 
332     while( (i > 0) && (*src != 0) )
333     {
334         *dest++ = *src++;
335         i--;
336     }
337 
338     return (maxlen - i);
339 }
340 
341 /*---------------------------------------------------------------------------*
342   Name:         entryToPath
343 
344   Description:  Convert from fst entry to path. The result is not null
345                 terminated.
346 
347   Arguments:    entry       FST entry
348                 path        pointer to store the result
349                 maxlen      size of the buffer start from path
350 
351   Returns:      Num of chars copied
352  *---------------------------------------------------------------------------*/
entryToPath(ARCHandle * handle,u32 entry,char * path,u32 maxlen)353 static u32 entryToPath(ARCHandle* handle, u32 entry, char* path, u32 maxlen)
354 {
355     char*       name;
356     u32         loc;
357     FSTEntry*    FSTEntries;
358 
359     FSTEntries = (FSTEntry*)handle->FSTStart;
360 
361     if (entry == 0)
362     {
363         return 0;
364     }
365 
366     name = handle->FSTStringStart + stringOff(FSTEntries, entry);
367 
368     loc = entryToPath(handle, parentDir(FSTEntries, entry), path, maxlen);
369 
370     if (loc == maxlen)
371     {
372         return loc;
373     }
374 
375     *(path + loc++) = '/';
376 
377     loc += myStrncpy(path + loc, name, maxlen - loc);
378 
379     return loc;
380 }
381 
382 
383 /*---------------------------------------------------------------------------*
384   Name:         ARCConvertEntrynumToPath
385 
386   Description:  Convert from fst entry to path.
387 
388   Arguments:    entrynum    FST entry
389                 path        pointer to store the result
390                 maxlen      size of the buffer start from path
391 
392   Returns:      TRUE if all the path fits in maxlen. FALSE if not (in this
393                 case, it's truncated to fit maxlen).
394  *---------------------------------------------------------------------------*/
ARCConvertEntrynumToPath(ARCHandle * handle,s32 entrynum,char * path,u32 maxlen)395 static BOOL ARCConvertEntrynumToPath(ARCHandle* handle, s32 entrynum, char* path, u32 maxlen)
396 {
397     u32         loc;
398     FSTEntry*    FSTEntries;
399 
400 
401     FSTEntries = (FSTEntry*)handle->FSTStart;
402     ASSERTMSG1((0 <= entrynum) && (entrynum < handle->entryNum),
403                "ARCConvertEntrynumToPath: specified entrynum(%d) is out of range  ",
404                 entrynum );
405     ASSERTMSG1(1 < maxlen, "ARCConvertEntrynumToPath: maxlen should be more than 1 (%d is specified)",
406                maxlen );
407     // currently only directories can be converted to path
408     // finding the directory where a file is located is not so easy
409     // while finding the parent directory of a directory is a piece of cake.
410     ASSERTMSG(entryIsDir(FSTEntries, entrynum),
411               "ARCConvertEntrynumToPath: cannot convert an entry num for a file to path  ");
412 
413     loc = entryToPath(handle, (u32)entrynum, path, maxlen);
414 
415     if (loc == maxlen)
416     {
417         // Overwrite the last char with NULL
418         path[maxlen - 1] = '\0';
419         return FALSE;
420     }
421 
422     // For directories, put '/' at the last
423     if (entryIsDir(FSTEntries, entrynum))
424     {
425         if (loc == maxlen - 1)
426         {
427             // There's no room to put the last '/', so just put '\0' and return FALSE
428             path[loc] = '\0';
429             return FALSE;
430         }
431 
432         path[loc++] = '/';
433     }
434 
435     path[loc] = '\0';
436     return TRUE;
437 }
438 
439 /*---------------------------------------------------------------------------*
440   Name:         ARCGetCurrentDir
441 
442   Description:  Get current directory
443 
444   Arguments:    path        pointer to store the result
445                 maxlen      size of the buffer start from path
446 
447   Returns:      TRUE if all the path fits in maxlen. FALSE if not (in this
448                 case, it's truncated to fit maxlen).
449  *---------------------------------------------------------------------------*/
ARCGetCurrentDir(ARCHandle * handle,char * path,u32 maxlen)450 BOOL ARCGetCurrentDir(ARCHandle* handle, char* path, u32 maxlen)
451 {
452     ASSERTMSG1( 1 < maxlen, "ARCGetCurrentDir: maxlen should be more than 1 (%d is specified)",
453                 maxlen );
454 
455     return ARCConvertEntrynumToPath(handle, (s32)handle->currDir, path, maxlen);
456 }
457 
ARCGetStartAddrInMem(ARCFileInfo * af)458 void* ARCGetStartAddrInMem(ARCFileInfo* af)
459 {
460     ARCHandle* handle;
461 
462     handle = af->handle;
463 
464     ASSERTMSG(handle, "ARCGetFileAddr(): af->handle is null pointer. Maybe it's not initialized properly");
465     ASSERTMSG(af, "ARCGetFileAddr(): null pointer is specified to ARCFileInfo structure");
466 
467     return (void*)( (u32)handle->archiveStartAddr + af->startOffset );
468 }
469 
ARCGetStartOffset(ARCFileInfo * af)470 u32 ARCGetStartOffset(ARCFileInfo* af)
471 {
472     return af->startOffset;
473 }
474 
ARCGetLength(ARCFileInfo * af)475 u32 ARCGetLength(ARCFileInfo* af)
476 {
477     return af->length;
478 }
479 
480 
ARCClose(ARCFileInfo * af)481 BOOL ARCClose(ARCFileInfo* af)
482 {
483 #pragma unused(af)
484 
485     return TRUE;
486 }
487 
ARCChangeDir(ARCHandle * handle,const char * dirName)488 BOOL ARCChangeDir(ARCHandle* handle, const char* dirName)
489 {
490     s32         entry;
491 #ifdef _DEBUG
492     char        currentDir[128];
493 #endif
494     FSTEntry*   FSTEntries;
495 
496     ASSERTMSG(handle, "ARCChangeDir(): null pointer is specified to ARCHandle");
497     ASSERTMSG(dirName, "ARCChangeDir(): null pointer is specified to dirname");
498 
499     entry = ARCConvertPathToEntrynum(handle, dirName);
500     FSTEntries = (FSTEntry*)handle->FSTStart;
501 
502 #ifdef _DEBUG
503     if (0 > entry)
504     {
505         ARCGetCurrentDir(handle, currentDir, 128);
506         OSPanic(__FILE__, __LINE__, "ARCOpendir(): directory '%s' is not found under %s  ",
507                 dirName, currentDir);
508     }
509 #endif
510     ASSERTMSG1(entryIsDir(FSTEntries, entry), "ARCChangeDir(): %s is not a directory", dirName );
511 
512     if ( (entry < 0) || (entryIsDir(FSTEntries, entry) == FALSE) )
513     {
514         return FALSE;
515     }
516 
517     handle->currDir = (u32)entry;
518 
519     return TRUE;
520 }
521 
522 
ARCOpenDir(ARCHandle * handle,const char * dirName,ARCDir * dir)523 BOOL ARCOpenDir(ARCHandle* handle, const char* dirName, ARCDir* dir)
524 {
525     s32         entry;
526     FSTEntry*   FSTEntries;
527 #ifdef _DEBUG
528     char        currentDir[128];
529 #endif
530 
531     ASSERTMSG(handle, "ARCOpenDir(): null pointer is specified to ARCHandle");
532     ASSERTMSG(dirName, "ARCOpenDir(): null pointer is specified to ARCDir");
533 
534     entry = ARCConvertPathToEntrynum(handle, dirName);
535     FSTEntries = (FSTEntry*)handle->FSTStart;
536 
537 #ifdef _DEBUG
538     if (entry < 0)
539     {
540         ARCGetCurrentDir(handle, currentDir, 128);
541         OSPanic(__FILE__, __LINE__,
542                 "ARCOpendir(): directory '%s' is not found under %s  ",
543                 dirName,
544                 currentDir);
545     }
546 #endif
547 
548     ASSERTMSG1( entryIsDir(FSTEntries, entry), "ARCOpenDir(): %s is a regular file", dirName );
549 
550     if ( (entry < 0) || (entryIsDir(FSTEntries, entry) == FALSE) )
551         return FALSE;
552 
553     dir->handle = handle;
554     dir->entryNum = (u32)entry;
555     dir->location = (u32)entry + 1;
556     dir->next = nextDir(FSTEntries, entry);
557 
558     return TRUE;
559 }
560 
ARCReadDir(ARCDir * dir,ARCDirEntry * dirent)561 BOOL ARCReadDir(ARCDir* dir, ARCDirEntry* dirent)
562 {
563     u32         loc;
564     FSTEntry*   FSTEntries;
565     ARCHandle*  handle;
566 
567     handle = dir->handle;
568 
569     ASSERTMSG(handle, "ARCReadDir: dir->handle is null pointer. Maybe it's not initialized properly");
570 
571     FSTEntries = (FSTEntry*)handle->FSTStart;
572 
573     // Check the next location. loc == dir->next means it reached the
574     // end of the directory. Other check is for illegal setting by ARCSeekDir.
575     loc = dir->location;
576 retry:
577     if ( (loc <= dir->entryNum) || (dir->next <= loc) )
578         return FALSE;
579 
580     dirent->handle = handle;
581     dirent->entryNum = loc;
582     dirent->isDir = entryIsDir(FSTEntries, loc);
583     dirent->name = handle->FSTStringStart + stringOff(FSTEntries, loc);
584 
585     // check "."
586     // support archive files created by "darch[D] -c ."
587     if (dirent->name[0] == '.' && dirent->name[1] == '\0') {
588         loc++;
589         goto retry;
590     }
591 
592     dir->location = entryIsDir(FSTEntries, loc)? nextDir(FSTEntries, loc) : (loc+1);
593 
594     return TRUE;
595 }
596 
ARCCloseDir(ARCDir * dir)597 BOOL ARCCloseDir(ARCDir* dir)
598 {
599 #pragma unused (dir)
600 
601     return TRUE;
602 }
603