|
1 /* |
|
2 * High-level PhysicsFS archiver for simple unpacked file formats. |
|
3 * |
|
4 * This is a framework that basic archivers build on top of. It's for simple |
|
5 * formats that can just hand back a list of files and the offsets of their |
|
6 * uncompressed data. There are an alarming number of formats like this. |
|
7 * |
|
8 * RULES: Archive entries must be uncompressed, must not have separate subdir |
|
9 * entries (but can have subdirs), must be case insensitive LOW ASCII |
|
10 * filenames <= 56 bytes. No symlinks, etc. We can relax some of these rules |
|
11 * as necessary. |
|
12 * |
|
13 * Please see the file LICENSE.txt in the source's root directory. |
|
14 * |
|
15 * This file written by Ryan C. Gordon. |
|
16 */ |
|
17 |
|
18 #define __PHYSICSFS_INTERNAL__ |
|
19 #include "physfs_internal.h" |
|
20 |
|
21 typedef struct |
|
22 { |
|
23 PHYSFS_Io *io; |
|
24 PHYSFS_uint32 entryCount; |
|
25 UNPKentry *entries; |
|
26 } UNPKinfo; |
|
27 |
|
28 |
|
29 typedef struct |
|
30 { |
|
31 PHYSFS_Io *io; |
|
32 UNPKentry *entry; |
|
33 PHYSFS_uint32 curPos; |
|
34 } UNPKfileinfo; |
|
35 |
|
36 |
|
37 void UNPK_closeArchive(PHYSFS_Dir *opaque) |
|
38 { |
|
39 UNPKinfo *info = ((UNPKinfo *) opaque); |
|
40 info->io->destroy(info->io); |
|
41 allocator.Free(info->entries); |
|
42 allocator.Free(info); |
|
43 } /* UNPK_closeArchive */ |
|
44 |
|
45 |
|
46 static PHYSFS_sint64 UNPK_read(PHYSFS_Io *io, void *buffer, PHYSFS_uint64 len) |
|
47 { |
|
48 UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; |
|
49 const UNPKentry *entry = finfo->entry; |
|
50 const PHYSFS_uint64 bytesLeft = (PHYSFS_uint64)(entry->size-finfo->curPos); |
|
51 PHYSFS_sint64 rc; |
|
52 |
|
53 if (bytesLeft < len) |
|
54 len = bytesLeft; |
|
55 |
|
56 rc = finfo->io->read(finfo->io, buffer, len); |
|
57 if (rc > 0) |
|
58 finfo->curPos += (PHYSFS_uint32) rc; |
|
59 |
|
60 return rc; |
|
61 } /* UNPK_read */ |
|
62 |
|
63 |
|
64 static PHYSFS_sint64 UNPK_write(PHYSFS_Io *io, const void *b, PHYSFS_uint64 len) |
|
65 { |
|
66 BAIL_MACRO(PHYSFS_ERR_READ_ONLY, -1); |
|
67 } /* UNPK_write */ |
|
68 |
|
69 |
|
70 static PHYSFS_sint64 UNPK_tell(PHYSFS_Io *io) |
|
71 { |
|
72 return ((UNPKfileinfo *) io->opaque)->curPos; |
|
73 } /* UNPK_tell */ |
|
74 |
|
75 |
|
76 static int UNPK_seek(PHYSFS_Io *io, PHYSFS_uint64 offset) |
|
77 { |
|
78 UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; |
|
79 const UNPKentry *entry = finfo->entry; |
|
80 int rc; |
|
81 |
|
82 BAIL_IF_MACRO(offset >= entry->size, PHYSFS_ERR_PAST_EOF, 0); |
|
83 rc = finfo->io->seek(finfo->io, entry->startPos + offset); |
|
84 if (rc) |
|
85 finfo->curPos = (PHYSFS_uint32) offset; |
|
86 |
|
87 return rc; |
|
88 } /* UNPK_seek */ |
|
89 |
|
90 |
|
91 static PHYSFS_sint64 UNPK_length(PHYSFS_Io *io) |
|
92 { |
|
93 const UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; |
|
94 return ((PHYSFS_sint64) finfo->entry->size); |
|
95 } /* UNPK_length */ |
|
96 |
|
97 |
|
98 static PHYSFS_Io *UNPK_duplicate(PHYSFS_Io *_io) |
|
99 { |
|
100 UNPKfileinfo *origfinfo = (UNPKfileinfo *) _io->opaque; |
|
101 PHYSFS_Io *io = NULL; |
|
102 PHYSFS_Io *retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); |
|
103 UNPKfileinfo *finfo = (UNPKfileinfo *) allocator.Malloc(sizeof (UNPKfileinfo)); |
|
104 GOTO_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_duplicate_failed); |
|
105 GOTO_IF_MACRO(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_duplicate_failed); |
|
106 |
|
107 io = origfinfo->io->duplicate(origfinfo->io); |
|
108 if (!io) goto UNPK_duplicate_failed; |
|
109 finfo->io = io; |
|
110 finfo->entry = origfinfo->entry; |
|
111 finfo->curPos = 0; |
|
112 memcpy(retval, _io, sizeof (PHYSFS_Io)); |
|
113 retval->opaque = finfo; |
|
114 return retval; |
|
115 |
|
116 UNPK_duplicate_failed: |
|
117 if (finfo != NULL) allocator.Free(finfo); |
|
118 if (retval != NULL) allocator.Free(retval); |
|
119 if (io != NULL) io->destroy(io); |
|
120 return NULL; |
|
121 } /* UNPK_duplicate */ |
|
122 |
|
123 static int UNPK_flush(PHYSFS_Io *io) { return 1; /* no write support. */ } |
|
124 |
|
125 static void UNPK_destroy(PHYSFS_Io *io) |
|
126 { |
|
127 UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; |
|
128 finfo->io->destroy(finfo->io); |
|
129 allocator.Free(finfo); |
|
130 allocator.Free(io); |
|
131 } /* UNPK_destroy */ |
|
132 |
|
133 |
|
134 static const PHYSFS_Io UNPK_Io = |
|
135 { |
|
136 CURRENT_PHYSFS_IO_API_VERSION, NULL, |
|
137 UNPK_read, |
|
138 UNPK_write, |
|
139 UNPK_seek, |
|
140 UNPK_tell, |
|
141 UNPK_length, |
|
142 UNPK_duplicate, |
|
143 UNPK_flush, |
|
144 UNPK_destroy |
|
145 }; |
|
146 |
|
147 |
|
148 static int entryCmp(void *_a, size_t one, size_t two) |
|
149 { |
|
150 if (one != two) |
|
151 { |
|
152 const UNPKentry *a = (const UNPKentry *) _a; |
|
153 return __PHYSFS_stricmpASCII(a[one].name, a[two].name); |
|
154 } /* if */ |
|
155 |
|
156 return 0; |
|
157 } /* entryCmp */ |
|
158 |
|
159 |
|
160 static void entrySwap(void *_a, size_t one, size_t two) |
|
161 { |
|
162 if (one != two) |
|
163 { |
|
164 UNPKentry tmp; |
|
165 UNPKentry *first = &(((UNPKentry *) _a)[one]); |
|
166 UNPKentry *second = &(((UNPKentry *) _a)[two]); |
|
167 memcpy(&tmp, first, sizeof (UNPKentry)); |
|
168 memcpy(first, second, sizeof (UNPKentry)); |
|
169 memcpy(second, &tmp, sizeof (UNPKentry)); |
|
170 } /* if */ |
|
171 } /* entrySwap */ |
|
172 |
|
173 |
|
174 static PHYSFS_sint32 findStartOfDir(UNPKinfo *info, const char *path, |
|
175 int stop_on_first_find) |
|
176 { |
|
177 PHYSFS_sint32 lo = 0; |
|
178 PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1); |
|
179 PHYSFS_sint32 middle; |
|
180 PHYSFS_uint32 dlen = (PHYSFS_uint32) strlen(path); |
|
181 PHYSFS_sint32 retval = -1; |
|
182 const char *name; |
|
183 int rc; |
|
184 |
|
185 if (*path == '\0') /* root dir? */ |
|
186 return 0; |
|
187 |
|
188 if ((dlen > 0) && (path[dlen - 1] == '/')) /* ignore trailing slash. */ |
|
189 dlen--; |
|
190 |
|
191 while (lo <= hi) |
|
192 { |
|
193 middle = lo + ((hi - lo) / 2); |
|
194 name = info->entries[middle].name; |
|
195 rc = __PHYSFS_strnicmpASCII(path, name, dlen); |
|
196 if (rc == 0) |
|
197 { |
|
198 char ch = name[dlen]; |
|
199 if (ch < '/') /* make sure this isn't just a substr match. */ |
|
200 rc = -1; |
|
201 else if (ch > '/') |
|
202 rc = 1; |
|
203 else |
|
204 { |
|
205 if (stop_on_first_find) /* Just checking dir's existance? */ |
|
206 return middle; |
|
207 |
|
208 if (name[dlen + 1] == '\0') /* Skip initial dir entry. */ |
|
209 return (middle + 1); |
|
210 |
|
211 /* there might be more entries earlier in the list. */ |
|
212 retval = middle; |
|
213 hi = middle - 1; |
|
214 } /* else */ |
|
215 } /* if */ |
|
216 |
|
217 if (rc > 0) |
|
218 lo = middle + 1; |
|
219 else |
|
220 hi = middle - 1; |
|
221 } /* while */ |
|
222 |
|
223 return retval; |
|
224 } /* findStartOfDir */ |
|
225 |
|
226 |
|
227 /* |
|
228 * Moved to seperate function so we can use alloca then immediately throw |
|
229 * away the allocated stack space... |
|
230 */ |
|
231 static void doEnumCallback(PHYSFS_EnumFilesCallback cb, void *callbackdata, |
|
232 const char *odir, const char *str, PHYSFS_sint32 ln) |
|
233 { |
|
234 char *newstr = __PHYSFS_smallAlloc(ln + 1); |
|
235 if (newstr == NULL) |
|
236 return; |
|
237 |
|
238 memcpy(newstr, str, ln); |
|
239 newstr[ln] = '\0'; |
|
240 cb(callbackdata, odir, newstr); |
|
241 __PHYSFS_smallFree(newstr); |
|
242 } /* doEnumCallback */ |
|
243 |
|
244 |
|
245 void UNPK_enumerateFiles(PHYSFS_Dir *opaque, const char *dname, |
|
246 int omitSymLinks, PHYSFS_EnumFilesCallback cb, |
|
247 const char *origdir, void *callbackdata) |
|
248 { |
|
249 UNPKinfo *info = ((UNPKinfo *) opaque); |
|
250 PHYSFS_sint32 dlen, dlen_inc, max, i; |
|
251 |
|
252 i = findStartOfDir(info, dname, 0); |
|
253 if (i == -1) /* no such directory. */ |
|
254 return; |
|
255 |
|
256 dlen = (PHYSFS_sint32) strlen(dname); |
|
257 if ((dlen > 0) && (dname[dlen - 1] == '/')) /* ignore trailing slash. */ |
|
258 dlen--; |
|
259 |
|
260 dlen_inc = ((dlen > 0) ? 1 : 0) + dlen; |
|
261 max = (PHYSFS_sint32) info->entryCount; |
|
262 while (i < max) |
|
263 { |
|
264 char *add; |
|
265 char *ptr; |
|
266 PHYSFS_sint32 ln; |
|
267 char *e = info->entries[i].name; |
|
268 if ((dlen) && |
|
269 ((__PHYSFS_strnicmpASCII(e, dname, dlen)) || (e[dlen] != '/'))) |
|
270 { |
|
271 break; /* past end of this dir; we're done. */ |
|
272 } /* if */ |
|
273 |
|
274 add = e + dlen_inc; |
|
275 ptr = strchr(add, '/'); |
|
276 ln = (PHYSFS_sint32) ((ptr) ? ptr-add : strlen(add)); |
|
277 doEnumCallback(cb, callbackdata, origdir, add, ln); |
|
278 ln += dlen_inc; /* point past entry to children... */ |
|
279 |
|
280 /* increment counter and skip children of subdirs... */ |
|
281 while ((++i < max) && (ptr != NULL)) |
|
282 { |
|
283 char *e_new = info->entries[i].name; |
|
284 if ((__PHYSFS_strnicmpASCII(e, e_new, ln) != 0) || |
|
285 (e_new[ln] != '/')) |
|
286 { |
|
287 break; |
|
288 } /* if */ |
|
289 } /* while */ |
|
290 } /* while */ |
|
291 } /* UNPK_enumerateFiles */ |
|
292 |
|
293 |
|
294 /* |
|
295 * This will find the UNPKentry associated with a path in platform-independent |
|
296 * notation. Directories don't have UNPKentries associated with them, but |
|
297 * (*isDir) will be set to non-zero if a dir was hit. |
|
298 */ |
|
299 static UNPKentry *findEntry(const UNPKinfo *info, const char *path, int *isDir) |
|
300 { |
|
301 UNPKentry *a = info->entries; |
|
302 PHYSFS_sint32 pathlen = (PHYSFS_sint32) strlen(path); |
|
303 PHYSFS_sint32 lo = 0; |
|
304 PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1); |
|
305 PHYSFS_sint32 middle; |
|
306 const char *thispath = NULL; |
|
307 int rc; |
|
308 |
|
309 while (lo <= hi) |
|
310 { |
|
311 middle = lo + ((hi - lo) / 2); |
|
312 thispath = a[middle].name; |
|
313 rc = __PHYSFS_strnicmpASCII(path, thispath, pathlen); |
|
314 |
|
315 if (rc > 0) |
|
316 lo = middle + 1; |
|
317 |
|
318 else if (rc < 0) |
|
319 hi = middle - 1; |
|
320 |
|
321 else /* substring match...might be dir or entry or nothing. */ |
|
322 { |
|
323 if (isDir != NULL) |
|
324 { |
|
325 *isDir = (thispath[pathlen] == '/'); |
|
326 if (*isDir) |
|
327 return NULL; |
|
328 } /* if */ |
|
329 |
|
330 if (thispath[pathlen] == '\0') /* found entry? */ |
|
331 return &a[middle]; |
|
332 /* adjust search params, try again. */ |
|
333 else if (thispath[pathlen] > '/') |
|
334 hi = middle - 1; |
|
335 else |
|
336 lo = middle + 1; |
|
337 } /* if */ |
|
338 } /* while */ |
|
339 |
|
340 if (isDir != NULL) |
|
341 *isDir = 0; |
|
342 |
|
343 BAIL_MACRO(PHYSFS_ERR_NO_SUCH_PATH, NULL); |
|
344 } /* findEntry */ |
|
345 |
|
346 |
|
347 PHYSFS_Io *UNPK_openRead(PHYSFS_Dir *opaque, const char *fnm, int *fileExists) |
|
348 { |
|
349 PHYSFS_Io *retval = NULL; |
|
350 UNPKinfo *info = (UNPKinfo *) opaque; |
|
351 UNPKfileinfo *finfo = NULL; |
|
352 int isdir = 0; |
|
353 UNPKentry *entry = findEntry(info, fnm, &isdir); |
|
354 |
|
355 *fileExists = (entry != NULL); |
|
356 GOTO_IF_MACRO(isdir, PHYSFS_ERR_NOT_A_FILE, UNPK_openRead_failed); |
|
357 GOTO_IF_MACRO(!entry, ERRPASS, UNPK_openRead_failed); |
|
358 |
|
359 retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); |
|
360 GOTO_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_openRead_failed); |
|
361 |
|
362 finfo = (UNPKfileinfo *) allocator.Malloc(sizeof (UNPKfileinfo)); |
|
363 GOTO_IF_MACRO(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_openRead_failed); |
|
364 |
|
365 finfo->io = info->io->duplicate(info->io); |
|
366 GOTO_IF_MACRO(!finfo->io, ERRPASS, UNPK_openRead_failed); |
|
367 |
|
368 if (!finfo->io->seek(finfo->io, entry->startPos)) |
|
369 goto UNPK_openRead_failed; |
|
370 |
|
371 finfo->curPos = 0; |
|
372 finfo->entry = entry; |
|
373 |
|
374 memcpy(retval, &UNPK_Io, sizeof (*retval)); |
|
375 retval->opaque = finfo; |
|
376 return retval; |
|
377 |
|
378 UNPK_openRead_failed: |
|
379 if (finfo != NULL) |
|
380 { |
|
381 if (finfo->io != NULL) |
|
382 finfo->io->destroy(finfo->io); |
|
383 allocator.Free(finfo); |
|
384 } /* if */ |
|
385 |
|
386 if (retval != NULL) |
|
387 allocator.Free(retval); |
|
388 |
|
389 return NULL; |
|
390 } /* UNPK_openRead */ |
|
391 |
|
392 |
|
393 PHYSFS_Io *UNPK_openWrite(PHYSFS_Dir *opaque, const char *name) |
|
394 { |
|
395 BAIL_MACRO(PHYSFS_ERR_READ_ONLY, NULL); |
|
396 } /* UNPK_openWrite */ |
|
397 |
|
398 |
|
399 PHYSFS_Io *UNPK_openAppend(PHYSFS_Dir *opaque, const char *name) |
|
400 { |
|
401 BAIL_MACRO(PHYSFS_ERR_READ_ONLY, NULL); |
|
402 } /* UNPK_openAppend */ |
|
403 |
|
404 |
|
405 int UNPK_remove(PHYSFS_Dir *opaque, const char *name) |
|
406 { |
|
407 BAIL_MACRO(PHYSFS_ERR_READ_ONLY, 0); |
|
408 } /* UNPK_remove */ |
|
409 |
|
410 |
|
411 int UNPK_mkdir(PHYSFS_Dir *opaque, const char *name) |
|
412 { |
|
413 BAIL_MACRO(PHYSFS_ERR_READ_ONLY, 0); |
|
414 } /* UNPK_mkdir */ |
|
415 |
|
416 |
|
417 int UNPK_stat(PHYSFS_Dir *opaque, const char *filename, |
|
418 int *exists, PHYSFS_Stat *stat) |
|
419 { |
|
420 int isDir = 0; |
|
421 const UNPKinfo *info = (const UNPKinfo *) opaque; |
|
422 const UNPKentry *entry = findEntry(info, filename, &isDir); |
|
423 |
|
424 if (isDir) |
|
425 { |
|
426 *exists = 1; |
|
427 stat->filetype = PHYSFS_FILETYPE_DIRECTORY; |
|
428 stat->filesize = 0; |
|
429 } /* if */ |
|
430 else if (entry != NULL) |
|
431 { |
|
432 *exists = 1; |
|
433 stat->filetype = PHYSFS_FILETYPE_REGULAR; |
|
434 stat->filesize = entry->size; |
|
435 } /* else if */ |
|
436 else |
|
437 { |
|
438 *exists = 0; |
|
439 return 0; |
|
440 } /* else */ |
|
441 |
|
442 stat->modtime = -1; |
|
443 stat->createtime = -1; |
|
444 stat->accesstime = -1; |
|
445 stat->readonly = 1; |
|
446 |
|
447 return 1; |
|
448 } /* UNPK_stat */ |
|
449 |
|
450 |
|
451 PHYSFS_Dir *UNPK_openArchive(PHYSFS_Io *io, UNPKentry *e, |
|
452 const PHYSFS_uint32 num) |
|
453 { |
|
454 UNPKinfo *info = (UNPKinfo *) allocator.Malloc(sizeof (UNPKinfo)); |
|
455 if (info == NULL) |
|
456 { |
|
457 allocator.Free(e); |
|
458 BAIL_MACRO(PHYSFS_ERR_OUT_OF_MEMORY, NULL); |
|
459 } /* if */ |
|
460 |
|
461 __PHYSFS_sort(e, (size_t) num, entryCmp, entrySwap); |
|
462 info->io = io; |
|
463 info->entryCount = num; |
|
464 info->entries = e; |
|
465 |
|
466 return info; |
|
467 } /* UNPK_openArchive */ |
|
468 |
|
469 /* end of archiver_unpacked.c ... */ |
|
470 |