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