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