1 /*
2 * Copyright (C) 2003-2010 Neverball authors
3 *
4 * NEVERBALL is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published
6 * by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 */
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <assert.h>
18 #include <string.h>
19
20 #include "fs.h"
21 #include "dir.h"
22 #include "array.h"
23 #include "common.h"
24
25 /*
26 * This file implements the high-level virtual file system layer
27 * routines.
28 */
29
30 /*---------------------------------------------------------------------------*/
31
cmp_dir_items(const void * A,const void * B)32 static int cmp_dir_items(const void *A, const void *B)
33 {
34 const struct dir_item *a = A, *b = B;
35 return strcmp(a->path, b->path);
36 }
37
is_archive(struct dir_item * item)38 static int is_archive(struct dir_item *item)
39 {
40 return (str_ends_with(item->path, ".zip") ||
41 str_ends_with(item->path, ".pk3"));
42 }
43
add_archives(const char * path)44 static void add_archives(const char *path)
45 {
46 Array archives;
47 int i;
48
49 if ((archives = dir_scan(path, is_archive, NULL, NULL)))
50 {
51 array_sort(archives, cmp_dir_items);
52
53 for (i = 0; i < array_len(archives); i++)
54 fs_add_path(DIR_ITEM_GET(archives, i)->path);
55
56 dir_free(archives);
57 }
58 }
59
fs_add_path_with_archives(const char * path)60 int fs_add_path_with_archives(const char *path)
61 {
62 add_archives(path);
63 return fs_add_path(path);
64 }
65
66 /*---------------------------------------------------------------------------*/
67
fs_rename(const char * src,const char * dst)68 int fs_rename(const char *src, const char *dst)
69 {
70 const char *write_dir;
71 char *real_src, *real_dst;
72 int rc = 0;
73
74 if ((write_dir = fs_get_write_dir()))
75 {
76 real_src = concat_string(write_dir, "/", src, NULL);
77 real_dst = concat_string(write_dir, "/", dst, NULL);
78
79 rc = file_rename(real_src, real_dst);
80
81 free(real_src);
82 free(real_dst);
83 }
84
85 return rc;
86 }
87
88 /*---------------------------------------------------------------------------*/
89
fs_getc(fs_file fh)90 int fs_getc(fs_file fh)
91 {
92 unsigned char c;
93
94 if (fs_read(&c, 1, 1, fh) != 1)
95 return -1;
96
97 return (int) c;
98 }
99
fs_putc(int c,fs_file fh)100 int fs_putc(int c, fs_file fh)
101 {
102 unsigned char b = (unsigned char) c;
103
104 if (fs_write(&b, 1, 1, fh) != 1)
105 return -1;
106
107 return b;
108 }
109
fs_puts(const char * src,fs_file fh)110 int fs_puts(const char *src, fs_file fh)
111 {
112 while (*src)
113 if (fs_putc(*src++, fh) < 0)
114 return -1;
115
116 return 0;
117 }
118
fs_gets(char * dst,int count,fs_file fh)119 char *fs_gets(char *dst, int count, fs_file fh)
120 {
121 char *s = dst;
122 int c;
123
124 assert(dst);
125 assert(count > 0);
126
127 if (fs_eof(fh))
128 return NULL;
129
130 while (count > 1)
131 if ((c = fs_getc(fh)) >= 0)
132 {
133 count--;
134
135 *s = c;
136
137 /* Keep a newline and break. */
138
139 if (*s == '\n')
140 {
141 s++;
142 break;
143 }
144
145 /* Ignore carriage returns. */
146
147 if (*s == '\r')
148 {
149 count++;
150 s--;
151 }
152
153 s++;
154 }
155 else if (s == dst)
156 return NULL;
157 else
158 break;
159
160 *s = '\0';
161
162 return dst;
163 }
164
165 /*---------------------------------------------------------------------------*/
166
167 /*
168 * Write out a multiline string to a file with appropriately converted
169 * linefeed characters.
170 */
write_lines(const char * start,int length,fs_file fh)171 static int write_lines(const char *start, int length, fs_file fh)
172 {
173 #ifdef _WIN32
174 static const char crlf[] = "\r\n";
175 #else
176 static const char crlf[] = "\n";
177 #endif
178
179 int total_written = 0;
180
181 int datalen;
182 int written;
183 char *lf;
184
185 while (total_written < length)
186 {
187 lf = strchr(start, '\n');
188
189 datalen = lf ? (int) (lf - start) : length - total_written;
190 written = fs_write(start, 1, datalen, fh);
191
192 if (written < 0)
193 break;
194
195 total_written += written;
196
197 if (written < datalen)
198 break;
199
200 if (lf)
201 {
202 if (fs_puts(crlf, fh) < 0)
203 break;
204
205 total_written += 1;
206 start = lf + 1;
207 }
208 }
209
210 return total_written;
211 }
212
213 /*---------------------------------------------------------------------------*/
214
fs_printf(fs_file fh,const char * fmt,...)215 int fs_printf(fs_file fh, const char *fmt, ...)
216 {
217 char *buff;
218 int len;
219
220 va_list ap;
221
222 va_start(ap, fmt);
223 len = 1 + vsnprintf(NULL, 0, fmt, ap);
224 va_end(ap);
225
226 if ((buff = malloc(len)))
227 {
228 int written;
229
230 va_start(ap, fmt);
231 vsnprintf(buff, len, fmt, ap);
232 va_end(ap);
233
234 /*
235 * HACK. This assumes fs_printf is always called with the
236 * intention of writing text, and not arbitrary data.
237 */
238
239 written = write_lines(buff, strlen(buff), fh);
240
241 free(buff);
242
243 return written;
244 }
245
246 return 0;
247 }
248
249 /*---------------------------------------------------------------------------*/
250
fs_load(const char * path,int * datalen)251 void *fs_load(const char *path, int *datalen)
252 {
253 fs_file fh;
254 void *data;
255
256 data = NULL;
257
258 if ((fh = fs_open(path, "r")))
259 {
260 if ((*datalen = fs_length(fh)) > 0)
261 {
262 if ((data = malloc(*datalen)))
263 {
264 if (fs_read(data, *datalen, 1, fh) != 1)
265 {
266 free(data);
267 data = NULL;
268 *datalen = 0;
269 }
270 }
271 }
272
273 fs_close(fh);
274 }
275
276 return data;
277 }
278
279 /*---------------------------------------------------------------------------*/
280
281 /*
282 * Convert a system path into a VFS path.
283 */
fs_resolve(const char * system)284 const char *fs_resolve(const char *system)
285 {
286 static char path[MAXSTR];
287
288 const char *p;
289
290 /*
291 * PhysicsFS will claim a file doesn't exist if its path uses a
292 * directory separator other than a forward slash, even if that
293 * separator is valid for the system. We'll oblige.
294 */
295
296 SAFECPY(path, system);
297
298 path_normalize(path);
299
300 if (fs_exists(path))
301 return path;
302
303 /* Chop off directories until we have a match. */
304
305 p = path;
306
307 while ((p = path_next_sep(p)))
308 {
309 /* Skip separator. */
310
311 p += 1;
312
313 if (fs_exists(p))
314 return p;
315 }
316
317 return NULL;
318 }
319
320 /*---------------------------------------------------------------------------*/
321