]> git.draconx.ca Git - liblbx.git/blob - src/image.c
liblbx: Add a hard check for image leadin versus frame count.
[liblbx.git] / src / image.c
1 /*
2  * 2ooM: The Master of Orion II Reverse Engineering Project
3  * Library for working with LBX image files.
4  * Copyright © 2006-2011, 2013-2014 Nick Bowler
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include <config.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <stdbool.h>
24 #include <inttypes.h>
25 #include <errno.h>
26
27 #include "pack.h"
28 #include "misc.h"
29 #include "lbx.h"
30 #include "error.h"
31 #include "image.h"
32
33 #define FLAG_RAW       0x0100 /* Image is stored as a flat array of bytes. */
34 #define FLAG_OVERWRITE 0x0400 /* Draw each frame on a clean slate (unsure). */
35 #define FLAG_BUILDING  0x0800 /* Buildings have this, related to shadow? */
36 #define FLAG_PALETTE   0x1000 /* Image contains embedded palette. */
37 #define FLAG_LOOPING   0x2000 /* Loop over all frames in the image (unsure). */
38
39 #define FLAG_ALL (FLAG_RAW|FLAG_OVERWRITE|FLAG_BUILDING|FLAG_PALETTE|FLAG_LOOPING)
40
41 #define HDR_LEN 12
42
43 /* States for image readout */
44 enum {
45         READ_STATE_INIT,
46         READ_STATE_HEADER,
47         READ_STATE_DATA,
48         READ_STATE_DONE,
49 };
50
51 struct lbx_image_priv {
52         struct lbx_image pub;
53
54         unsigned short wtf, flags;
55         unsigned char  wtf2;
56
57         const struct lbx_file_ops *fops;
58         int (*dtor)(void *handle);
59         void *f;
60
61         /* State of frame readout */
62         unsigned currentx, currenty, currentn;
63         int read_state;
64
65         unsigned long offsets[];
66 };
67
68 static struct lbx_image_priv *lbx_img_init(unsigned char hdr[static HDR_LEN])
69 {
70         unsigned short nframes = unpack_16_le(hdr+6);
71         struct lbx_image_priv *img;
72
73         img = malloc(sizeof *img + sizeof img->offsets[0] * (nframes+1));
74         if (!img) {
75                 lbx_error_raise(LBX_ENOMEM);
76                 return NULL;
77         }
78
79         *img = (struct lbx_image_priv) {
80                 .pub.width  = unpack_16_le(hdr+0),
81                 .pub.height = unpack_16_le(hdr+2),
82                 .wtf        = unpack_16_le(hdr+4),
83                 .pub.frames = hdr[6],
84                 .wtf2       = hdr[7],
85                 .pub.leadin = hdr[8],
86                 .pub.chunk  = hdr[9],
87                 .flags      = unpack_16_le(hdr+10),
88         };
89
90         if (img->flags & FLAG_OVERWRITE)
91                 img->pub.chunk = 1;
92
93         if (img->flags & FLAG_LOOPING)
94                 img->pub.leadin = 0;
95
96         if (img->pub.leadin >= img->pub.frames) {
97                 lbx_error_raise(LBX_EFORMAT);
98                 free(img);
99                 return NULL;
100         }
101
102         return img;
103 }
104
105 struct lbx_image *lbx_img_open(void *f, const struct lbx_file_ops *fops,
106                                int (*destructor)(void *))
107 {
108         unsigned char hdr_buf[HDR_LEN];
109         struct lbx_image_priv *img;
110
111         if (fops->read(hdr_buf, sizeof hdr_buf, f) != sizeof hdr_buf) {
112                 if (fops->eof(f))
113                         lbx_error_raise(LBX_EEOF);
114                 return NULL;
115         }
116
117         img = lbx_img_init(hdr_buf);
118         if (!img)
119                 return NULL;
120
121         img->f    = f;
122         img->fops = fops;
123         img->dtor = destructor;
124
125         /*
126          * DEBUG ONLY.  These assertions exist to catch otherwise valid image
127          * files which differ from what I believe to be true of all LBX images.
128          * When we can decode every image, then these assertions should be
129          * replaced with constraints.
130          */
131         _lbx_assert(img->wtf  == 0); /* version? */
132         _lbx_assert(img->wtf2 == 0); /* very likely is simply reserved. */
133         _lbx_assert(!(img->flags & ~FLAG_ALL));
134
135         /* Read all offsets.  Should be merged with identical code in lbx.c */
136         for (unsigned i = 0; i <= img->pub.frames; i++) {
137                 unsigned char buf[4];
138
139                 if (fops->read(buf, sizeof buf, f) != sizeof buf) {
140                         if (fops->eof(f))
141                                 lbx_error_raise(LBX_EEOF);
142                         free(img);
143                         return NULL;
144                 }
145
146                 img->offsets[i] = unpack_32_le(buf);
147         }
148
149         return &img->pub;
150 }
151
152 static int pipe_close(void *f)
153 {
154         struct lbx_pipe_state *p = f;
155         int rc;
156
157         rc = fclose(p->f);
158         free(p);
159         return rc;
160 }
161
162 static int file_close(void *f)
163 {
164         return fclose((FILE *)f);
165 }
166
167 struct lbx_image *lbx_img_fopen(const char *file)
168 {
169         struct lbx_pipe_state *p;
170         FILE *f;
171
172         f = fopen(file, "rb");
173         if (!f) {
174                 lbx_error_raise(-errno);
175                 return NULL;
176         }
177
178         if (fseek(f, 0, SEEK_CUR) == 0)
179                 return lbx_img_open(f, &lbx_default_fops, file_close);
180
181         p = malloc(sizeof *p);
182         if (!p) {
183                 lbx_error_raise(LBX_ENOMEM);
184                 fclose(f);
185                 return NULL;
186         }
187
188         *p = (struct lbx_pipe_state) { .f = f };
189         return lbx_img_open(p, &lbx_pipe_fops, pipe_close);
190 }
191
192 int lbx_img_seek(struct lbx_image *pub, unsigned frame)
193 {
194         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
195
196         if (frame >= pub->frames) {
197                 lbx_error_raise(LBX_EINVAL);
198                 return -1;
199         }
200
201         if (img->fops->seek(img->f, img->offsets[frame], SEEK_SET)) {
202                 return -1;
203         }
204
205         if (!(img->flags & FLAG_RAW)) {
206                 unsigned char buf[4];
207
208                 /* Read frame header */
209                 if (img->fops->read(buf, 4, img->f) != 4) {
210                         if (img->fops->eof(img->f))
211                                 lbx_error_raise(LBX_EEOF);
212                         return -1;
213                 }
214
215                 if (unpack_16_le(buf) != 1) {
216                         lbx_error_raise(LBX_EFORMAT);
217                         return -1;
218                 }
219
220                 img->currentx = 0;
221                 img->currenty = unpack_16_le(buf+2);
222                 if (img->currenty > img->pub.height) {
223                         lbx_error_raise(LBX_EFORMAT);
224                         return -1;
225                 }
226         }
227
228         img->read_state = READ_STATE_HEADER;
229         return 0;
230 }
231
232 long lbx_img_read_row_header(struct lbx_image *pub, unsigned *x, unsigned *y)
233 {
234         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
235         unsigned short length, offset;
236         unsigned char buf[4];
237
238         if (img->read_state != READ_STATE_HEADER) {
239                 lbx_error_raise(LBX_EINVAL);
240                 return -1;
241         }
242
243         /* Raw images have no row headers */
244         if (img->flags & FLAG_RAW) {
245                 img->currentn = img->pub.width;
246                 *y = img->currenty++;
247                 *x = 0;
248
249                 if (*y < img->pub.height) {
250                         img->read_state = READ_STATE_DATA;
251                         return img->currentn;
252                 } else {
253                         img->read_state = READ_STATE_DONE;
254                         return 0;
255                 }
256         }
257
258         do {
259                 if (img->fops->read(buf, sizeof buf, img->f) != sizeof buf) {
260                         if (img->fops->eof(img->f))
261                                 lbx_error_raise(LBX_EEOF);
262                         return -1;
263                 }
264
265                 length = unpack_16_le(buf+0);
266                 offset = unpack_16_le(buf+2);
267
268                 if (length == 0) {
269                         if (offset == 1000) {
270                                 img->read_state = READ_STATE_DONE;
271                                 return 0;
272                         } else if (offset > img->pub.height - img->currenty) {
273                                 lbx_error_raise(LBX_EFORMAT);
274                                 return -1;
275                         }
276
277                         img->currenty += offset;
278                         img->currentx  = 0;
279                 }
280         } while (length == 0);
281
282         if (offset > img->pub.width - img->currentx) {
283                 lbx_error_raise(LBX_EFORMAT);
284                 return -1;
285         }
286         img->currentx += offset;
287
288         if (length > img->pub.width - img->currentx) {
289                 lbx_error_raise(LBX_EFORMAT);
290                 return -1;
291         }
292         img->currentn = length;
293
294         img->read_state = READ_STATE_DATA;
295         *x = img->currentx;
296         *y = img->currenty;
297
298         return img->currentn;
299 }
300
301 long lbx_img_read_row_data(struct lbx_image *pub, void *buf)
302 {
303         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
304
305         if (img->read_state != READ_STATE_DATA) {
306                 lbx_error_raise(LBX_EINVAL);
307                 return -1;
308         }
309
310         if (img->fops->read(buf, img->currentn, img->f) != img->currentn) {
311                 if (img->fops->eof(img->f))
312                         lbx_error_raise(LBX_EEOF);
313                 return -1;
314         }
315
316         if (!(img->flags & FLAG_RAW)) {
317                 /* Skip padding byte, if any */
318                 if (img->currentn % 2) {
319                         if (img->fops->seek(img->f, 1, SEEK_CUR))
320                                 return -1;
321                 }
322         }
323
324         img->read_state = READ_STATE_HEADER;
325         img->currentx += img->currentn;
326
327         return img->currentn;
328 }
329
330 static int read_palette(void *f, const struct lbx_file_ops *fops,
331                         struct lbx_colour *palette, unsigned count,
332                         bool external)
333 {
334         assert(count <= 256);
335         for (unsigned i = 0; i < count; i++) {
336                 unsigned char buf[4];
337
338                 if (fops->read(buf, 4, f) != 4) {
339                         if (fops->eof(f))
340                                 lbx_error_raise(LBX_EEOF);
341                         return -1;
342                 }
343
344                 if (buf[0] != external) {
345                         lbx_error_raise(LBX_EFORMAT);
346                         return -1;
347                 }
348
349                 palette[i] = (struct lbx_colour) {
350                         .red    = buf[1] & 0x3f,
351                         .green  = buf[2] & 0x3f,
352                         .blue   = buf[3] & 0x3f,
353                         .active = 1,
354                 };
355         }
356
357         return 0;
358 }
359
360 int lbx_img_loadpalette(void *f, const struct lbx_file_ops *fops,
361                         struct lbx_colour *palette)
362 {
363         return read_palette(f, fops, palette, 256, true);
364 }
365
366 int lbx_img_getpalette(struct lbx_image *pub, struct lbx_colour *out)
367 {
368         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
369         unsigned long palette_start, palette_count, palette_offset;
370         unsigned char buf[4];
371         int rc;
372
373         /* Do nothing if the image doesn't have embedded palette data. */
374         if (!(img->flags & FLAG_PALETTE))
375                 return 0;
376
377         palette_offset = 16 + 4ul * img->pub.frames;
378         if (img->fops->seek(img->f, palette_offset, SEEK_SET)) {
379                 return -1;
380         }
381
382         /* Read embedded palette header */
383         if (img->fops->read(buf, 4, img->f) < 4)
384                 goto readerr;
385
386         palette_start = unpack_16_le(buf+0);
387         palette_count = unpack_16_le(buf+2);
388         if (palette_start + palette_count > 256) {
389                 lbx_error_raise(LBX_EFORMAT);
390                 return -1;
391         }
392
393         if (out) {
394                 rc = read_palette(img->f, img->fops,
395                                   out+palette_start, palette_count,
396                                   false);
397                 if (rc < 0)
398                         return -1;
399         }
400
401         return palette_count;
402 readerr:
403         if (img->fops->eof(img->f))
404                 lbx_error_raise(LBX_EEOF);
405         return -1;
406 }
407
408 int lbx_img_close(struct lbx_image *pub)
409 {
410         struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
411         int rc = 0;
412
413         if (img && img->dtor) {
414                 rc = img->dtor(img->f);
415         }
416         free(img);
417
418         return rc;
419 }