added assfile
[raydungeon] / libs / assfile / mod_archive.c
1 /*
2 assfile - library for accessing assets with an fopen/fread-like interface
3 Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #include "assfile_impl.h"
23 #include "tar.h"
24
25 struct file_info {
26         struct tar_entry *tarent;
27         long roffs;
28         int eof;
29 };
30
31 static void *fop_open(const char *fname, void *udata);
32 static void fop_close(void *fp, void *udata);
33 static long fop_seek(void *fp, long offs, int whence, void *udata);
34 static long fop_read(void *fp, void *buf, long size, void *udata);
35
36
37 struct ass_fileops *ass_alloc_archive(const char *fname)
38 {
39         struct ass_fileops *fop;
40         struct tar *tar;
41
42         if(!(tar = malloc(sizeof *tar))) {
43                 return 0;
44         }
45         if(load_tar(tar, fname) == -1) {
46                 free(tar);
47                 return 0;
48         }
49
50         if(!(fop = malloc(sizeof *fop))) {
51                 return 0;
52         }
53         fop->udata = tar;
54         fop->open = fop_open;
55         fop->close = fop_close;
56         fop->seek = fop_seek;
57         fop->read = fop_read;
58         return fop;
59 }
60
61 void ass_free_archive(struct ass_fileops *fop)
62 {
63         close_tar(fop->udata);
64         free(fop->udata);
65         fop->udata = 0;
66 }
67
68 static void *fop_open(const char *fname, void *udata)
69 {
70         int i;
71         struct file_info *file;
72         struct tar *tar = udata;
73
74         for(i=0; i<tar->num_files; i++) {
75                 if(strcmp(fname, tar->files[i].path) == 0) {
76                         if(!(file = malloc(sizeof *file))) {
77                                 ass_errno = ENOMEM;
78                                 return 0;
79                         }
80                         file->tarent = tar->files + i;
81                         file->roffs = 0;
82                         file->eof = 0;
83                         return file;
84                 }
85         }
86
87         ass_errno = ENOENT;
88         return 0;
89 }
90
91 static void fop_close(void *fp, void *udata)
92 {
93         free(fp);
94 }
95
96 static long fop_seek(void *fp, long offs, int whence, void *udata)
97 {
98         long newoffs;
99         struct file_info *file = fp;
100
101         switch(whence) {
102         case SEEK_SET:
103                 newoffs = offs;
104                 break;
105
106         case SEEK_CUR:
107                 newoffs = file->roffs + offs;
108                 break;
109
110         case SEEK_END:
111                 newoffs = file->tarent->size + offs;
112                 break;
113
114         default:
115                 ass_errno = EINVAL;
116                 return -1;
117         }
118
119         if(newoffs < 0) {
120                 ass_errno = EINVAL;
121                 return -1;
122         }
123
124         file->eof = 0;
125         file->roffs = newoffs;
126         return newoffs;
127 }
128
129 static long fop_read(void *fp, void *buf, long size, void *udata)
130 {
131         struct tar *tar = udata;
132         struct file_info *file = fp;
133         long newoffs = file->roffs + size;
134         size_t rdbytes;
135
136         if(file->roffs >= file->tarent->size) {
137                 file->eof = 1;
138                 return -1;
139         }
140
141         if(newoffs > file->tarent->size) {
142                 size = file->tarent->size - file->roffs;
143                 newoffs = file->tarent->size;
144         }
145
146         if(fseek(tar->fp, file->tarent->offset + file->roffs, SEEK_SET) == -1) {
147                 fprintf(stderr, "assfile mod_archive: fop_read failed to seek to %ld (%ld + %ld)\n",
148                                 file->tarent->offset + file->roffs, file->tarent->offset, file->roffs);
149                 return -1;
150         }
151         if((rdbytes = fread(buf, 1, size, tar->fp)) < size) {
152                 fprintf(stderr, "assfile mod_archive: fop_read unexpected EOF while trying to read %ld bytes\n", size);
153                 size = rdbytes;
154         }
155         file->roffs = newoffs;
156         return size;
157 }