Fix interpolation in title screen replays
[neverball] / share / fs_common.c
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
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
38 static int is_archive(struct dir_item *item)
39 {
40     return strcmp(item->path + strlen(item->path) - 4, ".zip") == 0;
41 }
42
43 static void add_archives(const char *path)
44 {
45     Array archives;
46     int i;
47
48     if ((archives = dir_scan(path, is_archive, NULL, NULL)))
49     {
50         array_sort(archives, cmp_dir_items);
51
52         for (i = 0; i < array_len(archives); i++)
53             fs_add_path(DIR_ITEM_GET(archives, i)->path);
54
55         dir_free(archives);
56     }
57 }
58
59 int fs_add_path_with_archives(const char *path)
60 {
61     add_archives(path);
62     return fs_add_path(path);
63 }
64
65 /*---------------------------------------------------------------------------*/
66
67 int fs_rename(const char *src, const char *dst)
68 {
69     const char *write_dir;
70     char *real_src, *real_dst;
71     int rc = 0;
72
73     if ((write_dir = fs_get_write_dir()))
74     {
75         real_src = concat_string(write_dir, "/", src, NULL);
76         real_dst = concat_string(write_dir, "/", dst, NULL);
77
78         rc = file_rename(real_src, real_dst);
79
80         free(real_src);
81         free(real_dst);
82     }
83
84     return rc;
85 }
86
87 /*---------------------------------------------------------------------------*/
88
89 int fs_getc(fs_file fh)
90 {
91     unsigned char c;
92
93     if (fs_read(&c, 1, 1, fh) != 1)
94         return -1;
95
96     return (int) c;
97 }
98
99 int fs_putc(int c, fs_file fh)
100 {
101     unsigned char b = (unsigned char) c;
102
103     if (fs_write(&b, 1, 1, fh) != 1)
104         return -1;
105
106     return b;
107 }
108
109 int fs_puts(const char *src, fs_file fh)
110 {
111     while (*src)
112         if (fs_putc(*src++, fh) < 0)
113             return -1;
114
115     return 0;
116 }
117
118 char *fs_gets(char *dst, int count, fs_file fh)
119 {
120     char *s = dst;
121     int c;
122
123     assert(dst);
124     assert(count > 0);
125
126     if (fs_eof(fh))
127         return NULL;
128
129     while (count > 1)
130         if ((c = fs_getc(fh)) >= 0)
131         {
132             count--;
133
134             *s = c;
135
136             /* Keep a newline and break. */
137
138             if (*s == '\n')
139             {
140                 s++;
141                 break;
142             }
143
144             /* Ignore carriage returns. */
145
146             if (*s == '\r')
147             {
148                 count++;
149                 s--;
150             }
151
152             s++;
153         }
154         else if (s == dst)
155             return NULL;
156         else
157             break;
158
159     *s = '\0';
160
161     return dst;
162 }
163
164 /*---------------------------------------------------------------------------*/
165
166 /*
167  * Write out a multiline string to a file with appropriately converted
168  * linefeed characters.
169  */
170 static int write_lines(const char *start, int length, fs_file fh)
171 {
172 #ifdef _WIN32
173     static const char crlf[] = "\r\n";
174 #else
175     static const char crlf[] = "\n";
176 #endif
177
178     int total_written = 0;
179
180     int datalen;
181     int written;
182     char *lf;
183
184     while (total_written < length)
185     {
186         lf = strchr(start, '\n');
187
188         datalen = lf ? (int) (lf - start) : length - total_written;
189         written = fs_write(start, 1, datalen, fh);
190
191         if (written < 0)
192             break;
193
194         total_written += written;
195
196         if (written < datalen)
197             break;
198
199         if (lf)
200         {
201             if (fs_puts(crlf, fh) < 0)
202                 break;
203
204             total_written += 1;
205             start = lf + 1;
206         }
207     }
208
209     return total_written;
210 }
211
212 /*---------------------------------------------------------------------------*/
213
214 /*
215  * Trying to avoid defining a feature test macro for every platform by
216  * declaring vsnprintf with the C99 signature.  This is probably bad.
217  */
218
219 #include <stdio.h>
220 #include <stdarg.h>
221 #ifndef __APPLE__
222 extern int vsnprintf(char *, size_t, const char *, va_list);
223 #endif
224
225 int fs_printf(fs_file fh, const char *fmt, ...)
226 {
227     char *buff;
228     int len;
229
230     va_list ap;
231
232     va_start(ap, fmt);
233     len = vsnprintf(NULL, 0, fmt, ap) + 1;
234     va_end(ap);
235
236     if ((buff = malloc(len)))
237     {
238         int written;
239
240         va_start(ap, fmt);
241         vsnprintf(buff, len, fmt, ap);
242         va_end(ap);
243
244         /*
245          * HACK.  This assumes fs_printf is always called with the
246          * intention of writing text, and not arbitrary data.
247          */
248
249         written = write_lines(buff, strlen(buff), fh);
250
251         free(buff);
252
253         return written;
254     }
255
256     return 0;
257 }
258
259 /*---------------------------------------------------------------------------*/
260
261 void *fs_load(const char *path, int *datalen)
262 {
263     fs_file fh;
264     void *data;
265
266     data = NULL;
267
268     if ((fh = fs_open(path, "r")))
269     {
270         if ((*datalen = fs_length(fh)) > 0)
271         {
272             if ((data = malloc(*datalen)))
273             {
274                 if (fs_read(data, *datalen, 1, fh) != 1)
275                 {
276                     free(data);
277                     data = NULL;
278                     *datalen = 0;
279                 }
280             }
281         }
282
283         fs_close(fh);
284     }
285
286     return data;
287 }
288
289 /*---------------------------------------------------------------------------*/
290
291 const char *fs_resolve(const char *path)
292 {
293     if (fs_exists(path))
294         return path;
295
296     /* Chop off directories until we have a match. */
297
298     while ((path = path_next_sep(path)))
299     {
300         /* Skip separator. */
301
302         path += 1;
303
304         if (fs_exists(path))
305             return path;
306     }
307
308     return NULL;
309 }
310
311 /*---------------------------------------------------------------------------*/