1 /*
2  * Copyright 2009-2017 Peter Kosyh <p.kosyh at gmail.com>
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation files
6  * (the "Software"), to deal in the Software without restriction,
7  * including without limitation the rights to use, copy, modify, merge,
8  * publish, distribute, sublicense, and/or sell copies of the Software,
9  * and to permit persons to whom the Software is furnished to do so,
10  * subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  *
23  */
24 
25 #include "system.h"
26 #include "idf.h"
27 #include "util.h"
28 #include "cache.h"
29 #include "list.h"
30 
31 typedef struct _idfd_t {
32 	struct list_head list;
33 	unsigned long offset;
34 	unsigned long size;
35 	idf_t			idf;
36 } idfd_t;
37 
38 
39 struct _idff_t {
40 	struct list_node list;
41 	idfd_t		*dir;
42 	unsigned long pos;
43 	FILE		*fd;
44 };
45 
46 struct _idf_t {
47 	unsigned long size;
48 	FILE	*fd;
49 	char 	*path;
50 	char	cwd[PATH_MAX];
51 	cache_t	dir;
52 	int		idfonly;
53 };
54 
free_idfd(void * p)55 static void free_idfd(void *p)
56 {
57 	idfd_t *dir = (idfd_t*)p;
58 	if (!p)
59 		return;
60 
61 	while (!list_empty(&dir->list)) {
62 		idff_t idff;
63 		idff = list_top(&dir->list, struct _idff_t, list);
64 		idf_close(idff);
65 	}
66 
67 	free(p);
68 }
69 
idf_done(idf_t idf)70 void idf_done(idf_t idf)
71 {
72 	if (!idf)
73 		return;
74 	if (idf->path)
75 		free(idf->path);
76 	if (idf->dir)
77 		cache_free(idf->dir);
78 	if (idf->fd)
79 		fclose(idf->fd);
80 	free(idf);
81 }
82 
idf_shrink(idf_t idf)83 void idf_shrink(idf_t idf)
84 {
85 	if (!idf)
86 		return;
87 	if (idf->dir)
88 		cache_shrink(idf->dir);
89 }
90 
read_word(FILE * fd,unsigned long * w)91 static int read_word(FILE *fd, unsigned long *w)
92 {
93 	unsigned char word[4];
94 	if (fread(word, 1, 4, fd) != 4)
95 		return -1;
96 	*w = (unsigned long)word[0] | ((unsigned long)word[1] << 8) |
97 		((unsigned long)word[2] << 16) |
98 		((unsigned long)word[3] << 24);
99 	return 0;
100 }
101 
write_word(FILE * fd,unsigned long w)102 static int write_word(FILE *fd, unsigned long w)
103 {
104 	char word[4];
105 
106 	word[0] = w & 0xff;
107 	word[1] = (w & 0xff00) >> 8;
108 	word[2] = (w & 0xff0000) >> 16;
109 	word[3] = (w & 0xff000000) >> 24;
110 
111 	if (fwrite(word, 1, 4, fd) != 4)
112 		return -1;
113 	return 0;
114 }
115 
idf_magic(const char * fname)116 int	idf_magic(const char *fname)
117 {
118 	char sign[4];
119 	FILE *fd = fopen(dirpath(fname), "rb");
120 	if (!fd)
121 		return 0;
122 	if (fread(sign, 1, 4, fd) != 4) {
123 		fclose(fd);
124 		return 0;
125 	}
126 	fclose(fd);
127 	if (!memcmp(sign, "IDF1", 4))
128 		return 1;
129 	return 0;
130 }
idf_setdir(idf_t idf,const char * path)131 int idf_setdir(idf_t idf, const char *path)
132 {
133 	if (idf && path)
134 		strcpy(idf->cwd, path);
135 	return 0;
136 }
137 
idf_getdir(idf_t idf)138 char *idf_getdir(idf_t idf)
139 {
140 	if (!idf)
141 		return NULL;
142 	return idf->cwd;
143 }
144 
idf_init(const char * fname)145 idf_t idf_init(const char *fname)
146 {
147 	char sign[4];
148 	unsigned long dir_size;
149 	char *fp = dirpath(fname);
150 	idf_t idf = malloc(sizeof(struct _idf_t));
151 	if (!idf)
152 		return NULL;
153 	memset(idf, 0, sizeof(struct _idf_t));
154 	idf->path = strdup(fname);
155 	if (!idf->path)
156 		goto err;
157 	idf->idfonly = 0;
158 	idf->fd = fopen(fp, "rb");
159 	idf->dir = cache_init(-1, free_idfd);
160 	idf->cwd[0] = 0;
161 	if (!idf->fd || !idf->dir)
162 		goto err;
163 	if (fseek(idf->fd, 0, SEEK_END))
164 		goto err;
165 	idf->size = ftell(idf->fd);
166 	if ((int)idf->size < 0)
167 		goto err;
168 	if (fseek(idf->fd, 0, SEEK_SET))
169 		goto err;
170 	if (fread(sign, 1, 4, idf->fd) != 4)
171 		goto err;
172 	if (memcmp(sign, "IDF1", 4))
173 		goto err;
174 	if (read_word(idf->fd, &dir_size))
175 		goto err;
176 	if (dir_size > idf->size)
177 		goto err;
178 	while (dir_size > 0) {
179 		unsigned long off;
180 		unsigned long size;
181 		unsigned char sz;
182 		char name[256];
183 		idfd_t *e;
184 		if (fread(&sz, 1, 1, idf->fd) != 1)
185 			goto err;
186 		if (fread(name, 1, sz, idf->fd) != sz)
187 			goto err;
188 		name[sz] = 0;
189 		if (read_word(idf->fd, &off))
190 			goto err;
191 		if (read_word(idf->fd, &size))
192 			goto err;
193 		e = malloc(sizeof(idfd_t));
194 		if (!e)
195 			goto err;
196 		e->offset = off;
197 		e->size = size;
198 		e->idf = idf;
199 		list_head_init(&e->list);
200 		if (cache_add(idf->dir, name, e)) {
201 			free(e);
202 			goto err;
203 		}
204 		cache_forget(idf->dir, e); /* use like hash */
205 /*		fprintf(stderr,"Parsed: '%s' @ %ld, %ld\n", name, off, size); */
206 		dir_size -= (1 + sz + 4 + 4);
207 	}
208 	return idf;
209 err:
210 	idf_done(idf);
211 	return NULL;
212 }
213 
214 typedef struct {
215 	struct list_node list;
216 	char *path;
217 	long size;
218 	struct list_head dir;
219 } idf_item_t;
220 
221 typedef struct {
222 	struct list_node list;
223 	char *dname;
224 } idf_dir_item_t;
225 
fcopy(FILE * to,const char * fname)226 static int fcopy(FILE *to, const char *fname)
227 {
228 	int rc = -1;
229 	char buff[4096];
230 	FILE *fd;
231 	fd = fopen(fname, "rb");
232 	if (!fd)
233 		return -1;
234 	while (!feof(fd)) {
235 		int s = fread(buff, 1, sizeof(buff), fd);
236 		if (!s) {
237 			if (feof(fd))
238 				break;
239 			goto err;
240 		}
241 		if (fwrite(buff, 1, s, to) != (size_t)s)
242 			goto err;
243 	}
244 	rc = 0;
245 err:
246 	fclose(fd);
247 	return rc;
248 }
249 
idf_tree(const char * path,struct list_head * list,const char * fname)250 static int idf_tree(const char *path, struct list_head *list, const char *fname)
251 {
252 	idf_item_t *dir;
253 	DIR *d;
254 	struct dirent *de;
255 	if (!path)
256 		return 0;
257 	d = opendir(dirpath(path));
258 	if (!d) {
259 		if (!access(dirpath(path), R_OK) && fname) {
260 			FILE *fd; idf_item_t *i;
261 			fd = fopen(dirpath(path), "rb");
262 			if (!fd) {
263 				fprintf(stderr, "Can not open file: '%s'\n", path);
264 				return -1;
265 			}
266 			i = malloc(sizeof(idf_item_t));
267 			if (!i) {
268 				fclose(fd);
269 				return -1;
270 			}
271 			/* list_head_init(&i->list); */
272 			if (!(i->path = strdup(fname)))
273 				goto err;
274 			if (fseek(fd, 0, SEEK_END) < 0)
275 				goto err;
276 			if ((i->size = ftell(fd)) < 0)
277 				goto err;
278 			list_head_init(&i->dir);
279 			fclose(fd);
280 			fprintf(stderr, "Added file: '%s' size: %ld\n", path, i->size);
281 			list_add(list, &i->list);
282 			return 0;
283 		err:
284 			if (i->path)
285 				free(i->path);
286 			free(i);
287 			return -1;
288 		}
289 		return 0;
290 	}
291 
292 	dir = malloc(sizeof(idf_item_t));
293 	if (!dir)
294 		goto err1;
295 	if (fname) {
296 		char *d = malloc(strlen(fname) + 2);
297 		if (!d)
298 			goto err2;
299 		strcpy(d, fname); strcat(d, "/");
300 		dir->path = d;
301 	} else {
302 		dir->path = strdup("/");
303 	}
304 	dir->size = 0;
305 	list_head_init(&dir->dir);
306 
307 	while ((de = readdir(d))) {
308 		char *p;
309 		idf_dir_item_t *di;
310 
311 		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
312 			continue;
313 		di = malloc(sizeof(idf_dir_item_t));
314 		if (di) {
315 			di->dname = strdup(de->d_name);
316 			list_add(&dir->dir, &di->list);
317 			dir->size += strlen(de->d_name) + 1;
318 		}
319 		p = getfilepath(path, de->d_name);
320 		if (p) {
321 			char *pp = getfilepath(fname, de->d_name);
322 			if (pp) {
323 				idf_tree(p, list, pp);
324 				free(pp);
325 			}
326 			free(p);
327 		}
328 	}
329 	list_add(list, &dir->list);
330 	fprintf(stderr, "Added dir: '%s' size: %ld\n", dir->path, dir->size);
331 	closedir(d);
332 	return 0;
333 err2:
334 	free(dir);
335 err1:
336 	closedir(d);
337 	return -1;
338 }
339 
idf_create(const char * file,const char * path)340 int idf_create(const char *file, const char *path)
341 {
342 	int rc = -1, i;
343 	FILE *fd;
344 	char *p;
345 	unsigned long off = 0;
346 	long dict_size = 0;
347 	idf_item_t *pos = NULL;
348 
349 	LIST_HEAD(items);
350 	p = strdup(path);
351 	if (!p)
352 		return -1;
353 
354 	unix_path(p);
355 
356 	i = strlen(p) - 1;
357 
358 	while (i >= 0 && p[i] == '/') {
359 		p[i --] = 0;
360 	}
361 
362 	idf_tree(p, &items, NULL);
363 
364 	free(p);
365 
366 	list_for_each(&items, pos, list) {
367 		idf_item_t *it = pos;
368 		dict_size += (1 + strlen(it->path) + 4 + 4);
369 	}
370 
371 	fd = fopen(dirpath(file), "wb");
372 	if (!fd)
373 		goto err;
374 	if (fwrite("IDF1", 1, 4, fd) != 4)
375 		goto err;
376 	if (write_word(fd, dict_size) < 0)
377 		goto err;
378 	off = 4 + 4 + dict_size;
379 
380 	list_for_each(&items, pos, list) {
381 		unsigned char s;
382 		idf_item_t *it = pos;
383 		s = strlen(it->path);
384 		if (fwrite(&s, 1, 1, fd) != 1)
385 			goto err;
386 		p = strdup(it->path);
387 		if (!p)
388 			goto err;
389 		tolow(p); /* in idf always lowcase */
390 		if (fwrite(p, 1, s, fd) != s) {
391 			free(p);
392 			goto err;
393 		}
394 		free(p);
395 		if (write_word(fd, off) < 0)
396 			goto err;
397 		if (write_word(fd, it->size) < 0)
398 			goto err;
399 		off += it->size;
400 	}
401 
402 	list_for_each(&items, pos, list) {
403 		idf_item_t *it = pos;
404 		char *p;
405 
406 		if (!list_empty(&it->dir)) { /* directory-file */
407 			idf_dir_item_t *d = NULL;
408 			list_for_each(&it->dir, d, list) {
409 				fprintf(fd, "%s\n", d->dname);
410 			}
411 			continue;
412 		}
413 
414 		p = getfilepath(path, it->path);
415 		if (p) {
416 			int rc = fcopy(fd, p);
417 			free(p);
418 			if (rc) {
419 				fprintf(stderr, "Error while copy file '%s'...\n", it->path);
420 				goto err;
421 			}
422 		}
423 	}
424 	rc = 0;
425 err:
426 	if (rc)
427 		fprintf(stderr, "Error creating idf file...\n");
428 
429 	while (!list_empty(&items)) {
430 		idf_item_t *it = list_top(&items, idf_item_t, list);
431 		while (!list_empty(&it->dir)) {
432 			idf_dir_item_t *di = list_top(&it->dir, idf_dir_item_t, list);
433 			free(di->dname);
434 			list_del(&di->list);
435 			free(di);
436 		}
437 		free(it->path);
438 		list_del(&it->list);
439 		free(it);
440 	}
441 	if (fd)
442 		fclose(fd);
443 	return rc;
444 }
445 
446 
idf_seek(idff_t fil,int offset,int whence)447 int idf_seek(idff_t fil, int offset, int whence)
448 {
449 	idfd_t *dir = fil->dir;
450 	switch (whence) {
451 	case SEEK_SET:
452 		if (offset < 0 || (unsigned int)offset > dir->size) {
453 			return -1;
454 		}
455 		fil->pos = offset;
456 		break;
457 	case SEEK_END:
458 		if (dir->size + offset > dir->size || (int)(dir->size + offset) < 0) {
459 			return -1;
460 		}
461 		fil->pos = dir->size + offset;
462 		break;
463 	case SEEK_CUR:
464 		if (fil->pos + offset > dir->size || (int)(fil->pos + offset) < 0) {
465 			return -1;
466 		}
467 		fil->pos += offset;
468 		break;
469 	}
470 	if (!fseek(fil->fd, fil->pos + dir->offset, SEEK_SET))
471 		return fil->pos;
472 	return -1;
473 }
474 
idf_close(idff_t fil)475 int idf_close(idff_t fil)
476 {
477 	if (fil) {
478 		fclose(fil->fd);
479 		list_del(&fil->list);
480 		free(fil);
481 	}
482 	return 0; /* nothing todo */
483 }
484 
485 #if 0
486 int idf_extract(idf_t idf, const char *fname)
487 {
488 	FILE *out;
489 	int size;
490 	idfd_t *dir = NULL;
491 	char buff[4096];
492 	if (idf)
493 		dir = cache_lookup(idf->dir, fname);
494 	if (!dir)
495 		return -1;
496 	fseek(idf->fd, dir->offset, SEEK_SET);
497 	out = fopen("out.bin", "wb");
498 	size = dir->size;
499 	while (size>0) {
500 		int s ;
501 		if (size < sizeof(buff))
502 			s = fread(buff, 1, size, idf->fd);
503 		else
504 			s = fread(buff, 1, sizeof(buff), idf->fd);
505 		fwrite(buff, 1, s, out);
506 		size -= s;
507 		fprintf(stderr, "size = %d\n", size);
508 	}
509 	fclose(out);
510 	return 0;
511 }
512 #endif
idf_eof(idff_t idf)513 int idf_eof(idff_t idf)
514 {
515 	if (!idf)
516 		return 1;
517 	if (idf->pos >= idf->dir->size)
518 		return 1;
519 	return 0;
520 }
521 
idf_error(idff_t idf)522 int idf_error(idff_t idf)
523 {
524 	if (!idf || !idf->fd)
525 		return -1;
526 	return ferror(idf->fd);
527 }
528 
idf_only(idf_t idf,int fl)529 int idf_only(idf_t idf, int fl)
530 {	int i;
531 	if (!idf)
532 		return -1;
533 	if (fl == -1)
534 		return idf->idfonly;
535 	i = idf->idfonly;
536 	idf->idfonly = fl;
537 	return i;
538 }
539 
idf_open(idf_t idf,const char * fname)540 idff_t idf_open(idf_t idf, const char *fname)
541 {
542 	char *rp;
543 	idfd_t *dir = NULL;
544 	idff_t fil = NULL;
545 	char *p;
546 	if (!idf || !fname)
547 		return NULL;
548 	p = strdup(fname);
549 	if (!p)
550 		return NULL;
551 	tolow(p);
552 	rp = getfilepath(idf->cwd, p);
553 	if (rp) {
554 		dir = cache_lookup(idf->dir, rp);
555 		free(rp);
556 	}
557 	free(p);
558 	if (!dir)
559 		return NULL;
560 
561 	fil = malloc(sizeof(struct _idff_t));
562 	if (!fil)
563 		return NULL;
564 /*	list_head_init(&fil->list); */
565 
566 	fil->dir = dir;
567 	fil->pos = 0;
568 	fil->fd = fopen(dirpath(idf->path), "rb");
569 	if (!fil->fd)
570 		goto err;
571 	list_add(&dir->list, &fil->list);
572 	return fil;
573 err:
574 	free(fil);
575 	return NULL;
576 }
577 
idf_access(idf_t idf,const char * fname)578 int idf_access(idf_t idf, const char *fname)
579 {
580 	char *rp;
581 	idfd_t *dir = NULL;
582 	if (idf) {
583 		rp = getfilepath(idf->cwd, fname);
584 		if (rp) {
585 			dir = cache_lookup(idf->dir, rp);
586 			free(rp);
587 		}
588 	}
589 	if (!dir)
590 		return -1;
591 	return 0;
592 }
593 
idf_opendir(idf_t idf,const char * dname)594 idff_t  idf_opendir(idf_t idf, const char *dname)
595 {
596 	return idf_open(idf, dname);
597 }
598 
idf_closedir(idff_t d)599 int idf_closedir(idff_t d)
600 {
601 	return idf_close(d);
602 }
603 
idf_readdir(idff_t d)604 char *idf_readdir(idff_t d)
605 {
606 	char buff[256];
607 	char *p = idf_gets(d, buff, sizeof(buff) - 1);
608 	if (!p)
609 		return p;
610 	return strdup(p);
611 }
612 
idf_gets(idff_t idf,char * b,int size)613 char *idf_gets(idff_t idf, char *b, int size)
614 {
615 	int rc, rc2;
616 	if (!idf)
617 		return NULL;
618 	if (!size)
619 		return NULL;
620 	rc = idf_read(idf, b, 1, size);
621 	if (rc < 0)
622 		return NULL;
623 	if (!rc && idf_eof(idf))
624 		return NULL;
625 	if (!idf_eof(idf))
626 		b[rc - 1] = 0;
627 	else {
628 		if (rc < size)
629 			b[rc] = 0;
630 		else
631 			b[size - 1] = 0;
632 	}
633 	rc2 = strcspn(b, "\n");
634 	b[rc2] = 0;
635 	idf_seek(idf, - (rc - rc2 - 1), SEEK_CUR);
636 	return b;
637 }
638 
idf_read(idff_t fil,void * ptr,int size,int maxnum)639 int idf_read(idff_t fil, void *ptr, int size, int maxnum)
640 {
641 	int rc = 0;
642 	long pos;
643 
644 	idfd_t *dir = fil->dir;
645 
646 	if (fseek(fil->fd, dir->offset + fil->pos, SEEK_SET) < 0) {
647 		return 0;
648 	}
649 #if 1
650 	while (maxnum) {
651 		pos = ftell(fil->fd);
652 		fil->pos = pos - dir->offset;
653 
654 		if (fil->pos + size > dir->size) {
655 			break;
656 		}
657 
658 		if (fread(ptr, size, 1, fil->fd) != 1)
659 			break;
660 /*		fil->pos += size; */
661 		ptr = (char *)ptr + size;
662 		maxnum --;
663 		rc ++;
664 	}
665 #else
666 	rc = fread(ptr, size, maxnum, fil->fd);
667 #endif
668 	pos = ftell(fil->fd);
669 	fil->pos = pos - dir->offset;
670 	return rc;
671 }
672 
673 #ifdef _USE_SDL
674 #include <SDL.h>
675 
676 #if SDL_VERSION_ATLEAST(2,0,0)
idfrw_seek(struct SDL_RWops * context,Sint64 offset,int whence)677 static Sint64 idfrw_seek(struct SDL_RWops *context, Sint64 offset, int whence)
678 #else
679 static int idfrw_seek(struct SDL_RWops *context, int offset, int whence)
680 #endif
681 {
682 	idff_t fil = (idff_t)context->hidden.unknown.data1;
683 	return idf_seek(fil, offset, whence);
684 }
685 #if SDL_VERSION_ATLEAST(2,0,0)
idfrw_read(struct SDL_RWops * context,void * ptr,size_t size,size_t maxnum)686 static size_t idfrw_read(struct SDL_RWops *context, void *ptr, size_t size, size_t maxnum)
687 #else
688 static int idfrw_read(struct SDL_RWops *context, void *ptr, int size, int maxnum)
689 #endif
690 {
691 	idff_t fil = (idff_t)context->hidden.unknown.data1;
692 	return idf_read(fil, ptr, size, maxnum);
693 }
694 
idfrw_close(struct SDL_RWops * context)695 static 	int idfrw_close(struct SDL_RWops *context)
696 {
697 	if (context) {
698 		idff_t fil = (idff_t)context->hidden.unknown.data1;
699 		idf_close(fil);
700 		SDL_FreeRW(context);
701 	}
702 	return 0;
703 }
704 #if SDL_VERSION_ATLEAST(2,0,0)
idfrw_size(struct SDL_RWops * context)705 static Sint64 idfrw_size(struct SDL_RWops *context)
706 {
707 	idff_t fil = (idff_t)context->hidden.unknown.data1;
708 	if (!fil || !fil->dir)
709 		return -1;
710 	return fil->dir->size;
711 }
712 #endif
713 
RWFromIdf(idf_t idf,const char * fname)714 SDL_RWops *RWFromIdf(idf_t idf, const char *fname)
715 {
716 	idff_t fil = NULL;
717 	SDL_RWops *n;
718 	fil = idf_open(idf, fname);
719 	if (!fil) {
720 		if (!idf || !idf->idfonly)
721 			return SDL_RWFromFile(dirpath(fname), "rb");
722 		return NULL;
723 	}
724 	n = SDL_AllocRW();
725 	if (!n)
726 		goto err;
727 #if SDL_VERSION_ATLEAST(2,0,0)
728 	n->size = idfrw_size;
729 #endif
730 	n->seek = idfrw_seek;
731 	n->read = idfrw_read;
732 	n->close = idfrw_close;
733 	n->hidden.unknown.data1 = fil;
734 	return n;
735 err:
736 	if (n)
737 		SDL_FreeRW(n);
738 	free(fil);
739 	return NULL;
740 }
741 #endif
742