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