/*
* 2ooM: The Master of Orion II Reverse Engineering Project
* Library for working with LBX image files.
* Copyright © 2006-2011, 2013-2014 Nick Bowler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include "pack.h"
#include "misc.h"
#include "lbx.h"
#include "error.h"
#include "image.h"
#define FLAG_RAW 0x0100 /* Image is stored as a flat array of bytes. */
#define FLAG_OVERWRITE 0x0400 /* Draw each frame on a clean slate (unsure). */
#define FLAG_BUILDING 0x0800 /* Buildings have this, related to shadow? */
#define FLAG_PALETTE 0x1000 /* Image contains embedded palette. */
#define FLAG_LOOPING 0x2000 /* Loop over all frames in the image (unsure). */
#define FLAG_ALL (FLAG_RAW|FLAG_OVERWRITE|FLAG_BUILDING|FLAG_PALETTE|FLAG_LOOPING)
#define HDR_LEN 12
/* States for image readout */
enum {
READ_STATE_INIT,
READ_STATE_HEADER,
READ_STATE_DATA,
READ_STATE_DONE,
};
struct lbx_image_priv {
struct lbx_image pub;
unsigned short wtf, flags;
unsigned char wtf2;
const struct lbx_file_ops *fops;
int (*dtor)(void *handle);
void *f;
/* State of frame readout */
unsigned currentx, currenty, currentn;
int read_state;
unsigned long offsets[];
};
static struct lbx_image_priv *lbx_img_init(unsigned char hdr[static HDR_LEN])
{
unsigned short nframes = unpack_16_le(hdr+6);
struct lbx_image_priv *img;
img = malloc(sizeof *img + sizeof img->offsets[0] * (nframes+1));
if (!img) {
lbx_error_raise(LBX_ENOMEM);
return NULL;
}
*img = (struct lbx_image_priv) {
.pub.width = unpack_16_le(hdr+0),
.pub.height = unpack_16_le(hdr+2),
.wtf = unpack_16_le(hdr+4),
.pub.frames = hdr[6],
.wtf2 = hdr[7],
.pub.leadin = hdr[8],
.pub.chunk = hdr[9],
.flags = unpack_16_le(hdr+10),
};
if (img->flags & FLAG_OVERWRITE)
img->pub.chunk = 1;
if (img->flags & FLAG_LOOPING)
img->pub.leadin = 0;
if (img->pub.leadin >= img->pub.frames) {
lbx_error_raise(LBX_EFORMAT);
free(img);
return NULL;
}
return img;
}
struct lbx_image *lbx_img_open(void *f, const struct lbx_file_ops *fops,
int (*destructor)(void *))
{
unsigned char hdr_buf[HDR_LEN];
struct lbx_image_priv *img;
if (fops->read(hdr_buf, sizeof hdr_buf, f) != sizeof hdr_buf) {
if (fops->eof(f))
lbx_error_raise(LBX_EEOF);
return NULL;
}
img = lbx_img_init(hdr_buf);
if (!img)
return NULL;
img->f = f;
img->fops = fops;
img->dtor = destructor;
/*
* DEBUG ONLY. These assertions exist to catch otherwise valid image
* files which differ from what I believe to be true of all LBX images.
* When we can decode every image, then these assertions should be
* replaced with constraints.
*/
_lbx_assert(img->wtf == 0); /* version? */
_lbx_assert(img->wtf2 == 0); /* very likely is simply reserved. */
_lbx_assert(!(img->flags & ~FLAG_ALL));
/* Read all offsets. Should be merged with identical code in lbx.c */
for (unsigned i = 0; i <= img->pub.frames; i++) {
unsigned char buf[4];
if (fops->read(buf, sizeof buf, f) != sizeof buf) {
if (fops->eof(f))
lbx_error_raise(LBX_EEOF);
free(img);
return NULL;
}
img->offsets[i] = unpack_32_le(buf);
}
return &img->pub;
}
static int pipe_close(void *f)
{
struct lbx_pipe_state *p = f;
int rc;
rc = fclose(p->f);
free(p);
return rc;
}
static int file_close(void *f)
{
return fclose((FILE *)f);
}
struct lbx_image *lbx_img_fopen(const char *file)
{
struct lbx_pipe_state *p;
FILE *f;
f = fopen(file, "rb");
if (!f) {
lbx_error_raise(-errno);
return NULL;
}
if (fseek(f, 0, SEEK_CUR) == 0)
return lbx_img_open(f, &lbx_default_fops, file_close);
p = malloc(sizeof *p);
if (!p) {
lbx_error_raise(LBX_ENOMEM);
fclose(f);
return NULL;
}
*p = (struct lbx_pipe_state) { .f = f };
return lbx_img_open(p, &lbx_pipe_fops, pipe_close);
}
int lbx_img_seek(struct lbx_image *pub, unsigned frame)
{
struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
if (frame >= pub->frames) {
lbx_error_raise(LBX_EINVAL);
return -1;
}
if (img->fops->seek(img->f, img->offsets[frame], SEEK_SET)) {
return -1;
}
if (!(img->flags & FLAG_RAW)) {
unsigned char buf[4];
/* Read frame header */
if (img->fops->read(buf, 4, img->f) != 4) {
if (img->fops->eof(img->f))
lbx_error_raise(LBX_EEOF);
return -1;
}
if (unpack_16_le(buf) != 1) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
img->currentx = 0;
img->currenty = unpack_16_le(buf+2);
if (img->currenty > img->pub.height) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
}
img->read_state = READ_STATE_HEADER;
return 0;
}
long lbx_img_read_row_header(struct lbx_image *pub, unsigned *x, unsigned *y)
{
struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
unsigned short length, offset;
unsigned char buf[4];
if (img->read_state != READ_STATE_HEADER) {
lbx_error_raise(LBX_EINVAL);
return -1;
}
/* Raw images have no row headers */
if (img->flags & FLAG_RAW) {
img->currentn = img->pub.width;
*y = img->currenty++;
*x = 0;
if (*y < img->pub.height) {
img->read_state = READ_STATE_DATA;
return img->currentn;
} else {
img->read_state = READ_STATE_DONE;
return 0;
}
}
do {
if (img->fops->read(buf, sizeof buf, img->f) != sizeof buf) {
if (img->fops->eof(img->f))
lbx_error_raise(LBX_EEOF);
return -1;
}
length = unpack_16_le(buf+0);
offset = unpack_16_le(buf+2);
if (length == 0) {
if (offset == 1000) {
img->read_state = READ_STATE_DONE;
return 0;
} else if (offset > img->pub.height - img->currenty) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
img->currenty += offset;
img->currentx = 0;
}
} while (length == 0);
if (offset > img->pub.width - img->currentx) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
img->currentx += offset;
if (length > img->pub.width - img->currentx) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
img->currentn = length;
img->read_state = READ_STATE_DATA;
*x = img->currentx;
*y = img->currenty;
return img->currentn;
}
long lbx_img_read_row_data(struct lbx_image *pub, void *buf)
{
struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
if (img->read_state != READ_STATE_DATA) {
lbx_error_raise(LBX_EINVAL);
return -1;
}
if (img->fops->read(buf, img->currentn, img->f) != img->currentn) {
if (img->fops->eof(img->f))
lbx_error_raise(LBX_EEOF);
return -1;
}
if (!(img->flags & FLAG_RAW)) {
/* Skip padding byte, if any */
if (img->currentn % 2) {
if (img->fops->seek(img->f, 1, SEEK_CUR))
return -1;
}
}
img->read_state = READ_STATE_HEADER;
img->currentx += img->currentn;
return img->currentn;
}
static int read_palette(void *f, const struct lbx_file_ops *fops,
struct lbx_colour *palette, unsigned count,
bool external)
{
assert(count <= 256);
for (unsigned i = 0; i < count; i++) {
unsigned char buf[4];
if (fops->read(buf, 4, f) != 4) {
if (fops->eof(f))
lbx_error_raise(LBX_EEOF);
return -1;
}
if (buf[0] != external) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
palette[i] = (struct lbx_colour) {
.red = buf[1] & 0x3f,
.green = buf[2] & 0x3f,
.blue = buf[3] & 0x3f,
.active = 1,
};
}
return 0;
}
int lbx_img_loadpalette(void *f, const struct lbx_file_ops *fops,
struct lbx_colour *palette)
{
return read_palette(f, fops, palette, 256, true);
}
int lbx_img_getpalette(struct lbx_image *pub, struct lbx_colour *out)
{
struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
unsigned long palette_start, palette_count, palette_offset;
unsigned char buf[4];
int rc;
/* Do nothing if the image doesn't have embedded palette data. */
if (!(img->flags & FLAG_PALETTE))
return 0;
palette_offset = 16 + 4ul * img->pub.frames;
if (img->fops->seek(img->f, palette_offset, SEEK_SET)) {
return -1;
}
/* Read embedded palette header */
if (img->fops->read(buf, 4, img->f) < 4)
goto readerr;
palette_start = unpack_16_le(buf+0);
palette_count = unpack_16_le(buf+2);
if (palette_start + palette_count > 256) {
lbx_error_raise(LBX_EFORMAT);
return -1;
}
if (out) {
rc = read_palette(img->f, img->fops,
out+palette_start, palette_count,
false);
if (rc < 0)
return -1;
}
return palette_count;
readerr:
if (img->fops->eof(img->f))
lbx_error_raise(LBX_EEOF);
return -1;
}
int lbx_img_close(struct lbx_image *pub)
{
struct lbx_image_priv *img = (struct lbx_image_priv *)pub;
int rc = 0;
if (img && img->dtor) {
rc = img->dtor(img->f);
}
free(img);
return rc;
}