/**
 
        libsmacker - A C library for decoding .smk Smacker Video files
 
        Copyright (C) 2012-2017 Greg Kennedy
 
 
 
        See smacker.h for more information.
 
 
 
        smacker.c
 
                Main implementation file of libsmacker.
 
                Open, close, query, render, advance and seek an smk
 
*/
 
 
 
#include "smacker.h"
 
 
 
/* safe malloc and free */
 
#include "smk_malloc.h"
 
 
 
/* data structures */
 
#include "smk_bitstream.h"
 
#include "smk_hufftree.h"
 
 
 
/* GLOBALS */
 
/* tree processing order */
 
#define SMK_TREE_MMAP 0
 
#define SMK_TREE_MCLR 1
 
#define SMK_TREE_FULL 2
 
#define SMK_TREE_TYPE 3
 
 
 
/* SMACKER DATA STRUCTURES */
 
struct smk_t {
 
    /* meta-info */
 
    /* file mode: see flags, smacker.h */
 
    unsigned char mode;
 
 
 
    /* microsec per frame - stored as a double to handle scaling
 
                (large positive millisec / frame values may overflow a ul) */
 
    double usf;
 
 
 
    /* total frames */
 
    unsigned long f;
 
    /* does file have a ring frame? (in other words, does file loop?) */
 
    unsigned char ring_frame;
 
 
 
    /* Index of current frame */
 
    unsigned long cur_frame;
 
 
 
    /* SOURCE union.
 
                Where the data is going to be read from (or be stored),
 
                depending on the file mode. */
 
    union {
 
        struct
 
        {
 
            /* on-disk mode */
 
            FILE* fp;
 
            unsigned long* chunk_offset;
 
        } file;
 
 
 
        /* in-memory mode: unprocessed chunks */
 
        unsigned char** chunk_data;
 
    } source;
 
 
 
    /* shared array of "chunk sizes"*/
 
    unsigned long* chunk_size;
 
 
 
    /* Holds per-frame flags (i.e. 'keyframe') */
 
    unsigned char* keyframe;
 
    /* Holds per-frame type mask (e.g. 'audio track 3, 2, and palette swap') */
 
    unsigned char* frame_type;
 
 
 
    /* video and audio structures */
 
    /* Video data type: enable/disable decode switch,
 
                video info and flags,
 
                pointer to last-decoded-palette */
 
    struct smk_video_t {
 
        /* enable/disable decode switch */
 
        unsigned char enable;
 
 
 
        /* video info */
 
        unsigned long w;
 
        unsigned long h;
 
        /* Y scale mode (constants defined in smacker.h)
 
                        0: unscaled
 
                        1: doubled
 
                        2: interlaced */
 
        unsigned char y_scale_mode;
 
 
 
        /* version ('2' or '4') */
 
        unsigned char v;
 
 
 
        /* Huffman trees */
 
        /* unsigned long tree_size[4]; */
 
        struct smk_huff16_t* tree[4];
 
 
 
        /* Palette data type: pointer to last-decoded-palette */
 
        unsigned char palette[256][3];
 
        /* Last-unpacked frame */
 
        unsigned char* frame;
 
    } video;
 
 
 
    /* audio structure */
 
    struct smk_audio_t {
 
        /* set if track exists in file */
 
        unsigned char exists;
 
 
 
        /* enable/disable switch (per track) */
 
        unsigned char enable;
 
 
 
        /* Info */
 
        unsigned char channels;
 
        unsigned char bitdepth;
 
        unsigned long rate;
 
        long max_buffer;
 
 
 
        /* compression type
 
                        0: raw PCM
 
                        1: SMK DPCM
 
                        2: Bink (Perceptual), unsupported */
 
        unsigned char compress;
 
 
 
        /* pointer to last-decoded-audio-buffer */
 
        void* buffer;
 
        unsigned long buffer_size;
 
    } audio[7];
 
};
 
 
 
union smk_read_t {
 
    FILE* file;
 
    unsigned char* ram;
 
};
 
 
 
/* An fread wrapper: consumes N bytes, or returns -1
 
        on failure (when size doesn't match expected) */
 
static char smk_read_file(void* buf, const size_t size, FILE* fp) {
 
    /* don't bother checking buf or fp, fread does it for us */
 
    size_t bytesRead 
= fread(buf
, 1, size
, fp
);  
    if (bytesRead != size) {
 
        fprintf(stderr
, "libsmacker::smk_read_file(buf,%lu,fp) - ERROR: Short read, %lu bytes returned\n\tReason: %s\n", (unsigned long)size
, (unsigned long)bytesRead
, strerror(errno
));  
        return -1;
 
    }
 
    return 0;
 
}
 
 
 
/* A memcpy wrapper: consumes N bytes, or returns -1
 
        on failure (when size too low) */
 
static char smk_read_memory(void* buf, const unsigned long size, unsigned char** p, unsigned long* p_size) {
 
    if (size > *p_size) {
 
        fprintf(stderr
, "libsmacker::smk_read_memory(buf,%lu,p,%lu) - ERROR: Short read\n", (unsigned long)size
, (unsigned long)*p_size
);  
        return -1;
 
    }
 
    *p += size;
 
    *p_size -= size;
 
    return 0;
 
}
 
 
 
/* Helper functions to do the reading, plus
 
        byteswap from LE to host order */
 
/* read n bytes from (source) into ret */
 
#define smk_read(ret, n)                                                                                                                                       \
 
    {                                                                                                                                                          \
 
        if (m) {                                                                                                                                               \
 
            r = (smk_read_file(ret, n, fp.file));                                                                                                              \
 
        } else {                                                                                                                                               \
 
            r = (smk_read_memory(ret, n, &fp.ram, &size));                                                                                                     \
 
        }                                                                                                                                                      \
 
        if (r < 0) {                                                                                                                                           \
 
            fprintf(stderr, "libsmacker::smk_read(...) - Errors encountered on read, bailing out (file: %s, line: %lu)\n", __FILE__, (unsigned long)__LINE__); \
 
            goto error;                                                                                                                                        \
 
        }                                                                                                                                                      \
 
    }
 
 
 
/* Calls smk_read, but returns a ul */
 
#define smk_read_ul(p)                                                                                                              \
 
    {                                                                                                                               \
 
        smk_read(buf, 4);                                                                                                           \
 
        p = ((unsigned long)buf[3] << 24) | ((unsigned long)buf[2] << 16) | ((unsigned long)buf[1] << 8) | ((unsigned long)buf[0]); \
 
    }
 
 
 
/* PUBLIC FUNCTIONS */
 
/* open an smk (from a generic Source) */
 
static smk smk_open_generic(const unsigned char m, union smk_read_t fp, unsigned long size, const unsigned char process_mode) {
 
    smk s = NULL;
 
 
 
    /* Temporary variables */
 
    long temp_l;
 
    unsigned long temp_u;
 
 
 
    /* r is used by macros above for return code */
 
    char r;
 
    unsigned char buf[4] = { '\0' };
 
 
 
    /* video hufftrees are stored as a large chunk (bitstream)
 
                these vars are used to load, then decode them */
 
    unsigned char* hufftree_chunk = NULL;
 
    unsigned long tree_size;
 
    /* a bitstream struct */
 
    struct smk_bit_t* bs = NULL;
 
 
 
    /* safe malloc the structure */
 
    smk_malloc(s, sizeof(struct smk_t));
 
 
 
    /* Check for a valid signature */
 
    smk_read(buf, 3);
 
    if (buf[0] != 'S' || buf[1] != 'M' || buf[2] != 'K') {
 
        fprintf(stderr
, "libsmacker::smk_open_generic - ERROR: invalid SMKn signature (got: %s)\n", buf
);  
        goto error;
 
    }
 
 
 
    /* Read .smk file version */
 
    smk_read(&s->video.v, 1);
 
    if (s->video.v != '2' && s->video.v != '4') {
 
        fprintf(stderr
, "libsmacker::smk_open_generic - Warning: invalid SMK version %c (expected: 2 or 4)\n", s
->video.
v);  
        /* take a guess */
 
        if (s->video.v < '4')
 
            s->video.v = '2';
 
        else
 
            s->video.v = '4';
 
        fprintf(stderr
, "\tProcessing will continue as type %c\n", s
->video.
v);  
    }
 
 
 
    /* width, height, total num frames */
 
    smk_read_ul(s->video.w);
 
    smk_read_ul(s->video.h);
 
 
 
    smk_read_ul(s->f);
 
 
 
    /* frames per second calculation */
 
    smk_read_ul(temp_u);
 
    temp_l = (int)temp_u;
 
    if (temp_l > 0) {
 
        /* millisec per frame */
 
        s->usf = temp_l * 1000;
 
    } else if (temp_l < 0) {
 
        /* 10 microsec per frame */
 
        s->usf = temp_l * -10;
 
    } else {
 
        /* defaults to 10 usf (= 100000 microseconds) */
 
        s->usf = 100000;
 
    }
 
 
 
    /* Video flags follow.
 
                Ring frame is important to libsmacker.
 
                Y scale / Y interlace go in the Video flags.
 
                The user should scale appropriately. */
 
    smk_read_ul(temp_u);
 
    if (temp_u & 0x01) {
 
        s->ring_frame = 1;
 
    }
 
    if (temp_u & 0x02) {
 
        s->video.y_scale_mode = SMK_FLAG_Y_DOUBLE;
 
    }
 
    if (temp_u & 0x04) {
 
        if (s->video.y_scale_mode == SMK_FLAG_Y_DOUBLE) {
 
            fputs("libsmacker::smk_open_generic - Warning: SMK file specifies both Y-Double AND Y-Interlace.\n", stderr
);  
        }
 
        s->video.y_scale_mode = SMK_FLAG_Y_INTERLACE;
 
    }
 
 
 
    /* Max buffer size for each audio track - used to pre-allocate buffers */
 
    for (temp_l = 0; temp_l < 7; temp_l++) {
 
        smk_read_ul(s->audio[temp_l].max_buffer);
 
    }
 
 
 
    /* Read size of "hufftree chunk" - save for later. */
 
    smk_read_ul(tree_size);
 
 
 
    /* "unpacked" sizes of each huff tree - we don't use
 
                but calling application might. */
 
    for (temp_l = 0; temp_l < 4; temp_l++) {
 
        /*              smk_read_ul(s->video.tree_size[temp_u]); */
 
        smk_read_ul(temp_u);
 
    }
 
 
 
    /* read audio rate data */
 
    for (temp_l = 0; temp_l < 7; temp_l++) {
 
        smk_read_ul(temp_u);
 
        if (temp_u & 0x40000000) {
 
            /* Audio track specifies "exists" flag, malloc structure and copy components. */
 
            s->audio[temp_l].exists = 1;
 
 
 
            /* and for all audio tracks */
 
            smk_malloc(s->audio[temp_l].buffer, s->audio[temp_l].max_buffer);
 
 
 
            if (temp_u & 0x80000000) {
 
                s->audio[temp_l].compress = 1;
 
            }
 
            s->audio[temp_l].bitdepth = ((temp_u & 0x20000000) ? 16 : 8);
 
            s->audio[temp_l].channels = ((temp_u & 0x10000000) ? 2 : 1);
 
            if (temp_u & 0x0c000000) {
 
                fprintf(stderr
, "libsmacker::smk_open_generic - Warning: audio track %ld is compressed with Bink (perceptual) Audio Codec: this is currently unsupported by libsmacker\n", temp_l
);  
                s->audio[temp_l].compress = 2;
 
            }
 
            /* Bits 25 & 24 are unused. */
 
            s->audio[temp_l].rate = (temp_u & 0x00FFFFFF);
 
        }
 
    }
 
 
 
    /* Skip over Dummy field */
 
    smk_read_ul(temp_u);
 
 
 
    /* FrameSizes and Keyframe marker are stored together. */
 
    smk_malloc(s->keyframe, (s->f + s->ring_frame));
 
    smk_malloc(s->chunk_size, (s->f + s->ring_frame) * sizeof(unsigned long));
 
 
 
    for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u++) {
 
        smk_read_ul(s->chunk_size[temp_u]);
 
 
 
        /* Set Keyframe */
 
        if (s->chunk_size[temp_u] & 0x01) {
 
            s->keyframe[temp_u] = 1;
 
        }
 
        /* Bits 1 is used, but the purpose is unknown. */
 
        s->chunk_size[temp_u] &= 0xFFFFFFFC;
 
    }
 
 
 
    /* That was easy... Now read FrameTypes! */
 
    smk_malloc(s->frame_type, (s->f + s->ring_frame));
 
    for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u++) {
 
        smk_read(&s->frame_type[temp_u], 1);
 
    }
 
 
 
    /* HuffmanTrees
 
                We know the sizes already: read and assemble into
 
                something actually parse-able at run-time */
 
    smk_malloc(hufftree_chunk, tree_size);
 
    smk_read(hufftree_chunk, tree_size);
 
 
 
    /* set up a Bitstream */
 
    bs = smk_bs_init(hufftree_chunk, tree_size);
 
    /* create some tables */
 
    for (temp_u = 0; temp_u < 4; temp_u++) {
 
        smk_huff16_build(bs, s->video.tree[temp_u]);
 
    }
 
 
 
    /* clean up */
 
    smk_free(bs);
 
    smk_free(hufftree_chunk);
 
 
 
    /* Go ahead and malloc storage for the video frame */
 
    smk_malloc(s->video.frame, s->video.w * s->video.h);
 
 
 
    /* final processing: depending on ProcessMode, handle what to do with rest of file data */
 
    s->mode = process_mode;
 
 
 
    /* Handle the rest of the data.
 
                For MODE_MEMORY, read the chunks and store */
 
    if (s->mode == SMK_MODE_MEMORY) {
 
        smk_malloc(s->source.chunk_data, (s->f + s->ring_frame) * sizeof(unsigned char*));
 
        for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u++) {
 
            smk_malloc(s->source.chunk_data[temp_u], s->chunk_size[temp_u]);
 
            smk_read(s->source.chunk_data[temp_u], s->chunk_size[temp_u]);
 
        }
 
    } else {
 
        /* MODE_STREAM: don't read anything now, just precompute offsets.
 
                        use fseek to verify that the file is "complete" */
 
        smk_malloc(s->source.file.chunk_offset, (s->f + s->ring_frame) * sizeof(unsigned long));
 
        for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u++) {
 
            s
->source.
file.
chunk_offset[temp_u
] = ftell(fp.
file); 
            if (fseek(fp.
file, s
->chunk_size
[temp_u
], SEEK_CUR
)) {  
                fprintf(stderr
, "libsmacker::smk_open - ERROR: fseek to frame %lu not OK.\n", temp_u
);  
                perror("\tError reported was");  
                goto error;
 
            }
 
        }
 
    }
 
 
 
    return s;
 
 
 
error:
 
    smk_free(bs);
 
    smk_free(hufftree_chunk);
 
    smk_close(s);
 
    return NULL;
 
}
 
 
 
/* open an smk (from a memory buffer) */
 
smk smk_open_memory(const unsigned char* buffer, const unsigned long size) {
 
    smk s = NULL;
 
 
 
    union smk_read_t fp;
 
 
 
    smk_assert(buffer);
 
 
 
    /* set up the read union for Memory mode */
 
    fp.ram = (unsigned char*)buffer;
 
 
 
    if (!(s = smk_open_generic(0, fp, size, SMK_MODE_MEMORY))) {
 
        fprintf(stderr
, "libsmacker::smk_open_memory(buffer,%lu) - ERROR: Fatal error in smk_open_generic, returning NULL.\n", size
);  
    }
 
 
 
    /* fall through, return s or null */
 
error:
 
    return s;
 
}
 
 
 
/* open an smk (from a file) */
 
smk smk_open_filepointer(FILE* file, const unsigned char mode) {
 
    smk s = NULL;
 
    union smk_read_t fp;
 
 
 
    smk_assert(file);
 
 
 
    /* Copy file ptr to internal union */
 
    fp.file = file;
 
 
 
    if (!(s = smk_open_generic(1, fp, 0, mode))) {
 
        fprintf(stderr
, "libsmacker::smk_open_filepointer(file,%u) - ERROR: Fatal error in smk_open_generic, returning NULL.\n", mode
);  
        goto error;
 
    }
 
 
 
    if (mode == SMK_MODE_MEMORY) {
 
    } else {
 
        s->source.file.fp = fp.file;
 
    }
 
 
 
    /* fall through, return s or null */
 
error:
 
    return s;
 
}
 
 
 
/* open an smk (from a file) */
 
smk smk_open_file(const char* filename, const unsigned char mode) {
 
    FILE* fp;
 
 
 
    smk_assert(filename);
 
 
 
    if (!(fp 
= fopen(filename
, "rb"))) {  
        // Jeff commented out error messages
 
        // fprintf(stderr,"libsmacker::smk_open_file(%s,%u) - ERROR: could not open file (errno: %d)\n",filename,mode,errno);
 
        // perror ("\tError reported was");
 
        goto error;
 
    }
 
 
 
    /* kick processing to smk_open_filepointer */
 
    return smk_open_filepointer(fp, mode);
 
 
 
    /* fall through, return s or null */
 
error:
 
    return NULL;
 
}
 
 
 
/* close out an smk file and clean up memory */
 
void smk_close(smk s) {
 
    unsigned long u;
 
 
 
    smk_assert(s);
 
 
 
    /* free video sub-components */
 
    {
 
        for (u = 0; u < 4; u++) {
 
            if (s->video.tree[u])
 
                smk_huff16_free(s->video.tree[u]);
 
        }
 
        smk_free(s->video.frame);
 
    }
 
 
 
    /* free audio sub-components */
 
    for (u = 0; u < 7; u++) {
 
        smk_free(s->audio[u].buffer);
 
    }
 
 
 
    smk_free(s->keyframe);
 
    smk_free(s->frame_type);
 
 
 
    if (s->mode == SMK_MODE_DISK) {
 
        /* disk-mode */
 
        if (s->source.file.fp) {
 
        }
 
        smk_free(s->source.file.chunk_offset);
 
    } else {
 
        /* mem-mode */
 
        if (s->source.chunk_data != NULL) {
 
            for (u = 0; u < (s->f + s->ring_frame); u++) {
 
                smk_free(s->source.chunk_data[u]);
 
            }
 
            smk_free(s->source.chunk_data);
 
        }
 
    }
 
    smk_free(s->chunk_size);
 
 
 
    smk_free(s);
 
 
 
error:;
 
}
 
 
 
/* tell some info about the file */
 
char smk_info_all(const smk object, unsigned long* frame, unsigned long* frame_count, double* usf) {
 
    /* sanity check */
 
    smk_assert(object);
 
    if (!frame && !frame_count && !usf) {
 
        fputs("libsmacker::smk_info_all(object,frame,frame_count,usf) - ERROR: Request for info with all-NULL return references\n", stderr
);  
        goto error;
 
    }
 
    if (frame)
 
        *frame = (object->cur_frame % object->f);
 
 
 
    if (frame_count)
 
        *frame_count = object->f;
 
 
 
    if (usf)
 
        *usf = object->usf;
 
 
 
    return 0;
 
 
 
error:
 
    return -1;
 
}
 
 
 
char smk_info_video(const smk object, unsigned long* w, unsigned long* h, unsigned char* y_scale_mode) {
 
    /* sanity check */
 
    smk_assert(object);
 
    if (!w && !h && !y_scale_mode) {
 
        fputs("libsmacker::smk_info_all(object,w,h,y_scale_mode) - ERROR: Request for info with all-NULL return references\n", stderr
);  
        return -1;
 
    }
 
 
 
    if (w)
 
        *w = object->video.w;
 
 
 
    if (h)
 
        *h = object->video.h;
 
 
 
    if (y_scale_mode)
 
        *y_scale_mode = object->video.y_scale_mode;
 
 
 
    return 0;
 
 
 
error:
 
    return -1;
 
}
 
 
 
char smk_info_audio(const smk object, unsigned char* track_mask, unsigned char channels[7], unsigned char bitdepth[7], unsigned long audio_rate[7]) {
 
    unsigned char i;
 
 
 
    /* sanity check */
 
    smk_assert(object);
 
 
 
    if (!track_mask && !channels && !bitdepth && !audio_rate) {
 
        fputs("libsmacker::smk_info_audio(object,track_mask,channels,bitdepth,audio_rate) - ERROR: Request for info with all-NULL return references\n", stderr
);  
        return -1;
 
    }
 
    if (track_mask) {
 
        *track_mask = ((object->audio[0].exists) | ((object->audio[1].exists) << 1) | ((object->audio[2].exists) << 2) | ((object->audio[3].exists) << 3) | ((object->audio[4].exists) << 4) | ((object->audio[5].exists) << 5) | ((object->audio[6].exists) << 6));
 
    }
 
    if (channels) {
 
        for (i = 0; i < 7; i++) {
 
            channels[i] = object->audio[i].channels;
 
        }
 
    }
 
    if (bitdepth) {
 
        for (i = 0; i < 7; i++) {
 
            bitdepth[i] = object->audio[i].bitdepth;
 
        }
 
    }
 
    if (audio_rate) {
 
        for (i = 0; i < 7; i++) {
 
            audio_rate[i] = object->audio[i].rate;
 
        }
 
    }
 
    return 0;
 
 
 
error:
 
    return -1;
 
}
 
 
 
/* Enable-disable switches */
 
char smk_enable_all(smk object, const unsigned char mask) {
 
    unsigned char i;
 
 
 
    /* sanity check */
 
    smk_assert(object);
 
 
 
    /* set video-enable */
 
    object->video.enable = (mask & 0x80);
 
 
 
    for (i = 0; i < 7; i++) {
 
        if (object->audio[i].exists) {
 
            object->audio[i].enable = (mask & (1 << i));
 
        }
 
    }
 
 
 
    return 0;
 
 
 
error:
 
    return -1;
 
}
 
 
 
char smk_enable_video(smk object, const unsigned char enable) {
 
    /* sanity check */
 
    smk_assert(object);
 
 
 
    object->video.enable = enable;
 
    return 0;
 
 
 
error:
 
    return -1;
 
}
 
 
 
char smk_enable_audio(smk object, const unsigned char track, const unsigned char enable) {
 
    /* sanity check */
 
    smk_assert(object);
 
 
 
    object->audio[track].enable = enable;
 
    return 0;
 
 
 
error:
 
    return -1;
 
}
 
 
 
const unsigned char* smk_get_palette(const smk object) {
 
    smk_assert(object);
 
 
 
    return (unsigned char*)object->video.palette;
 
 
 
error:
 
    return NULL;
 
}
 
const unsigned char* smk_get_video(const smk object) {
 
    smk_assert(object);
 
 
 
    return object->video.frame;
 
 
 
error:
 
    return NULL;
 
}
 
const unsigned char* smk_get_audio(const smk object, const unsigned char t) {
 
    smk_assert(object);
 
 
 
    return object->audio[t].buffer;
 
 
 
error:
 
    return NULL;
 
}
 
unsigned long smk_get_audio_size(const smk object, const unsigned char t) {
 
    smk_assert(object);
 
 
 
    return object->audio[t].buffer_size;
 
 
 
error:
 
    return 0;
 
}
 
 
 
/* Decompresses a palette-frame. */
 
static char smk_render_palette(struct smk_video_t* s, unsigned char* p, unsigned long size) {
 
    /* Index into palette */
 
    unsigned short i = 0;
 
    /* Helper variables */
 
    unsigned short count, src;
 
 
 
    static unsigned char oldPalette[256][3];
 
 
 
    /* Smacker palette map: smk colors are 6-bit, this table expands them to 8. */
 
    const unsigned char palmap[64] = {
 
        0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C,
 
        0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C,
 
        0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D,
 
        0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D,
 
        0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E,
 
        0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE,
 
        0xC3, 0xC7, 0xCB, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF,
 
        0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF
 
    };
 
 
 
    /* sanity check */
 
    smk_assert(s);
 
    smk_assert(p);
 
 
 
    // Copy palette to old palette
 
    memcpy(oldPalette
, s
->palette
, 256 * 3);  
 
 
    /* Loop until palette is complete, or we are out of bytes to process */
 
    while ((i < 256) && (size > 0)) {
 
        if ((*p) & 0x80) {
 
            /* 0x80: Skip block
 
                                (preserve C+1 palette entries from previous palette) */
 
            count = ((*p) & 0x7F) + 1;
 
            p++;
 
            size--;
 
 
 
            /* check for overflow condition */
 
            if (i + count > 256) {
 
                fprintf(stderr
, "libsmacker::palette_render(s,p,size) - ERROR: overflow, 0x80 attempt to skip %d entries from %d\n", count
, i
);  
                goto error;
 
            }
 
 
 
            /* finally: advance the index. */
 
            i += count;
 
        } else if ((*p) & 0x40) {
 
            /* 0x40: Color-shift block
 
                                Copy (c + 1) color entries of the previous palette,
 
                                starting from entry (s),
 
                                to the next entries of the new palette. */
 
            if (size < 2) {
 
                fputs("libsmacker::palette_render(s,p,size) - ERROR: 0x40 ran out of bytes for copy\n", stderr
);  
                goto error;
 
            }
 
 
 
            /* pick "count" items to copy */
 
            count = ((*p) & 0x3F) + 1;
 
            p++;
 
            size--;
 
 
 
            /* start offset of old palette */
 
            src = *p;
 
            p++;
 
            size--;
 
 
 
            /* overflow: see if we write/read beyond 256colors, or overwrite own palette */
 
            if (i + count > 256 || src + count > 256 || (src < i && src + count > i)) {
 
                fprintf(stderr
, "libsmacker::palette_render(s,p,size) - ERROR: overflow, 0x40 attempt to copy %d entries from %d to %d\n", count
, src
, i
);  
                goto error;
 
            }
 
 
 
            /* OK!  Copy the color-palette entries. */
 
            memmove(&s
->palette
[i
][0], &oldPalette
[src
][0], count 
* 3);  
 
 
            i += count;
 
        } else {
 
            /* 0x00: Set Color block
 
                                Direct-set the next 3 bytes for palette index */
 
            if (size < 3) {
 
                fprintf(stderr
, "libsmacker::palette_render - ERROR: 0x3F ran out of bytes for copy, size=%lu\n", size
);  
                goto error;
 
            }
 
 
 
            for (count = 0; count < 3; count++) {
 
                if (*p > 0x3F) {
 
                    fprintf(stderr
, "libsmacker::palette_render - ERROR: palette index exceeds 0x3F (entry [%u][%u])\n", i
, count
);  
                    goto error;
 
                }
 
                s->palette[i][count] = palmap[*p];
 
                p++;
 
                size--;
 
            }
 
            i++;
 
        }
 
    }
 
 
 
    if (i < 256) {
 
        fprintf(stderr
, "libsmacker::palette_render - ERROR: did not completely fill palette (idx=%u)\n", i
);  
        goto error;
 
    }
 
 
 
    return 0;
 
 
 
error:
 
    /* Error, return -1
 
                The new palette probably has errors but is preferrable to a black screen */
 
    return -1;
 
}
 
 
 
static char smk_render_video(struct smk_video_t* s, unsigned char* p, unsigned int size) {
 
    unsigned char* t = s->frame;
 
    unsigned char s1, s2;
 
    unsigned short temp;
 
    unsigned long i, j, k, row, col, skip;
 
 
 
    /* used for video decoding */
 
    struct smk_bit_t* bs = NULL;
 
 
 
    /* results from a tree lookup */
 
    long unpack;
 
 
 
    /* unpack, broken into pieces */
 
    unsigned char type;
 
    unsigned char blocklen;
 
    unsigned char typedata;
 
    char bit;
 
 
 
    const unsigned short sizetable[64] = {
 
        1, 2, 3, 4, 5, 6, 7, 8,
 
        9, 10, 11, 12, 13, 14, 15, 16,
 
        17, 18, 19, 20, 21, 22, 23, 24,
 
        25, 26, 27, 28, 29, 30, 31, 32,
 
        33, 34, 35, 36, 37, 38, 39, 40,
 
        41, 42, 43, 44, 45, 46, 47, 48,
 
        49, 50, 51, 52, 53, 54, 55, 56,
 
        57, 58, 59, 128, 256, 512, 1024, 2048
 
    };
 
 
 
    /* sanity check */
 
    smk_assert(s);
 
    smk_assert(p);
 
 
 
    row = 0;
 
    col = 0;
 
 
 
    /* Set up a bitstream for video unpacking */
 
    /* We could check the return code but it will only fail if p is null and we already verified that. */
 
    bs = smk_bs_init(p, size);
 
 
 
    /* Reset the cache on all bigtrees */
 
    smk_huff16_reset(s->tree[0]);
 
    smk_huff16_reset(s->tree[1]);
 
    smk_huff16_reset(s->tree[2]);
 
    smk_huff16_reset(s->tree[3]);
 
 
 
    while (row < s->h) {
 
        smk_huff16_lookup(bs, s->tree[SMK_TREE_TYPE], unpack);
 
 
 
        type = ((unpack & 0x0003));
 
        blocklen = ((unpack & 0x00FC) >> 2);
 
        typedata = ((unpack & 0xFF00) >> 8);
 
 
 
        /* support for v4 full-blocks */
 
        if (type == 1 && s->v == '4') {
 
            smk_bs_read_1(bs, bit);
 
            if (bit) {
 
                type = 4;
 
            } else {
 
                smk_bs_read_1(bs, bit);
 
                if (bit) {
 
                    type = 5;
 
                }
 
            }
 
        }
 
 
 
        for (j = 0; (j < sizetable[blocklen]) && (row < s->h); j++) {
 
            skip = (row * s->w) + col;
 
            switch (type) {
 
            case 0:
 
                smk_huff16_lookup(bs, s->tree[SMK_TREE_MCLR], unpack);
 
                s1 = (unpack & 0xFF00) >> 8;
 
                s2 = (unpack & 0x00FF);
 
                smk_huff16_lookup(bs, s->tree[SMK_TREE_MMAP], unpack);
 
 
 
                temp = 0x01;
 
                for (k = 0; k < 4; k++) {
 
                    for (i = 0; i < 4; i++) {
 
                        if (unpack & temp) {
 
                            t[skip + i] = s1;
 
                        } else {
 
                            t[skip + i] = s2;
 
                        }
 
                        temp = temp << 1;
 
                    }
 
                    skip += s->w;
 
                }
 
                break;
 
 
 
            case 1: /* FULL BLOCK */
 
                for (k = 0; k < 4; k++) {
 
                    smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack);
 
                    t[skip + 3] = ((unpack & 0xFF00) >> 8);
 
                    t[skip + 2] = (unpack & 0x00FF);
 
                    smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack);
 
                    t[skip + 1] = ((unpack & 0xFF00) >> 8);
 
                    t[skip] = (unpack & 0x00FF);
 
                    skip += s->w;
 
                }
 
                break;
 
            case 2: /* VOID BLOCK */
 
                /* break;
 
                                        if (s->frame)
 
                                        {
 
                                                memcpy(&t[skip], &s->frame[skip], 4);
 
                                                skip += s->w;
 
                                                memcpy(&t[skip], &s->frame[skip], 4);
 
                                                skip += s->w;
 
                                                memcpy(&t[skip], &s->frame[skip], 4);
 
                                                skip += s->w;
 
                                                memcpy(&t[skip], &s->frame[skip], 4);
 
                                        } */
 
                break;
 
            case 3: /* SOLID BLOCK */
 
                memset(&t
[skip
], typedata
, 4);  
                skip += s->w;
 
                memset(&t
[skip
], typedata
, 4);  
                skip += s->w;
 
                memset(&t
[skip
], typedata
, 4);  
                skip += s->w;
 
                memset(&t
[skip
], typedata
, 4);  
                break;
 
            case 4: /* V4 DOUBLE BLOCK */
 
                for (k = 0; k < 2; k++) {
 
                    smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack);
 
                    for (i = 0; i < 2; i++) {
 
                        memset(&t
[skip 
+ 2], (unpack 
& 0xFF00) >> 8, 2);  
                        memset(&t
[skip
], (unpack 
& 0x00FF), 2);  
                        skip += s->w;
 
                    }
 
                }
 
                break;
 
            case 5: /* V4 HALF BLOCK */
 
                for (k = 0; k < 2; k++) {
 
                    smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack);
 
                    t[skip + 3] = ((unpack & 0xFF00) >> 8);
 
                    t[skip + 2] = (unpack & 0x00FF);
 
                    t[skip + s->w + 3] = ((unpack & 0xFF00) >> 8);
 
                    t[skip + s->w + 2] = (unpack & 0x00FF);
 
                    smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack);
 
                    t[skip + 1] = ((unpack & 0xFF00) >> 8);
 
                    t[skip] = (unpack & 0x00FF);
 
                    t[skip + s->w + 1] = ((unpack & 0xFF00) >> 8);
 
                    t[skip + s->w] = (unpack & 0x00FF);
 
                    skip += (s->w << 1);
 
                }
 
                break;
 
            }
 
            col += 4;
 
            if (col >= s->w) {
 
                col = 0;
 
                row += 4;
 
            }
 
        }
 
    }
 
 
 
    smk_free(bs);
 
 
 
    return 0;
 
 
 
error:
 
    smk_free(bs);
 
    return -1;
 
}
 
 
 
/* Decompress audio track i. */
 
static char smk_render_audio(struct smk_audio_t* s, unsigned char* p, unsigned long size) {
 
    unsigned int j, k;
 
    unsigned char* t = s->buffer;
 
    struct smk_bit_t* bs = NULL;
 
 
 
    char bit;
 
    short unpack, unpack2;
 
 
 
    /* used for audio decoding */
 
    struct smk_huff8_t* aud_tree[4] = { NULL, NULL, NULL, NULL };
 
 
 
    /* sanity check */
 
    smk_assert(s);
 
    smk_assert(p);
 
 
 
    if (!s->compress) {
 
        /* Raw PCM data, update buffer size and malloc */
 
        s->buffer_size = size;
 
 
 
    } else if (s->compress == 1) {
 
        /* SMACKER DPCM compression */
 
        /* need at least 4 bytes to process */
 
        if (size < 4) {
 
            fputs("libsmacker::smk_render_audio() - ERROR: need 4 bytes to get unpacked output buffer size.\n", stderr
);  
            goto error;
 
        }
 
        /* chunk is compressed (huff-compressed dpcm), retrieve unpacked buffer size */
 
        s->buffer_size = ((unsigned int)p[3] << 24) | ((unsigned int)p[2] << 16) | ((unsigned int)p[1] << 8) | ((unsigned int)p[0]);
 
 
 
        p += 4;
 
        size -= 4;
 
 
 
        /* Compressed audio: must unpack here */
 
        /*  Set up a bitstream */
 
        bs = smk_bs_init(p, size);
 
 
 
        smk_bs_read_1(bs, bit);
 
 
 
        if (!bit) {
 
            fputs("libsmacker::smk_render_audio - ERROR: initial get_bit returned 0\n", stderr
);  
            goto error;
 
        }
 
 
 
        smk_bs_read_1(bs, bit);
 
        if (s->channels != (bit == 1 ? 2 : 1)) {
 
            fputs("libsmacker::smk_render - ERROR: mono/stereo mismatch\n", stderr
);  
        }
 
        smk_bs_read_1(bs, bit);
 
        if (s->bitdepth != (bit == 1 ? 16 : 8)) {
 
            fputs("libsmacker::smk_render - ERROR: 8-/16-bit mismatch\n", stderr
);  
        }
 
 
 
        /* build the trees */
 
        smk_huff8_build(bs, aud_tree[0]);
 
        j = 1;
 
        k = 1;
 
        if (s->bitdepth == 16) {
 
            smk_huff8_build(bs, aud_tree[1]);
 
            k = 2;
 
        }
 
        if (s->channels == 2) {
 
            smk_huff8_build(bs, aud_tree[2]);
 
            j = 2;
 
            k = 2;
 
            if (s->bitdepth == 16) {
 
                smk_huff8_build(bs, aud_tree[3]);
 
                k = 4;
 
            }
 
        }
 
 
 
        /* read initial sound level */
 
        if (s->channels == 2) {
 
            smk_bs_read_8(bs, unpack);
 
            if (s->bitdepth == 16) {
 
                smk_bs_read_8(bs, ((short*)t)[1])((short*)t)[1] |= (unpack << 8);
 
            } else {
 
                ((unsigned char*)t)[1] = (unsigned char)unpack;
 
            }
 
        }
 
        smk_bs_read_8(bs, unpack);
 
        if (s->bitdepth == 16) {
 
            smk_bs_read_8(bs, ((short*)t)[0])((short*)t)[0] |= (unpack << 8);
 
        } else {
 
            ((unsigned char*)t)[0] = (unsigned char)unpack;
 
        }
 
 
 
        /* All set: let's read some DATA! */
 
        while (k < s->buffer_size) {
 
            if (s->bitdepth == 8) {
 
                smk_huff8_lookup(bs, aud_tree[0], unpack);
 
                ((unsigned char*)t)[j] = (char)unpack + ((unsigned char*)t)[j - s->channels];
 
                j++;
 
                k++;
 
            } else {
 
                smk_huff8_lookup(bs, aud_tree[0], unpack);
 
                smk_huff8_lookup(bs, aud_tree[1], unpack2);
 
                ((short*)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short*)t)[j - s->channels];
 
                j++;
 
                k += 2;
 
            }
 
            if (s->channels == 2) {
 
                if (s->bitdepth == 8) {
 
                    smk_huff8_lookup(bs, aud_tree[2], unpack);
 
                    ((unsigned char*)t)[j] = (char)unpack + ((unsigned char*)t)[j - 2];
 
                    j++;
 
                    k++;
 
                } else {
 
                    smk_huff8_lookup(bs, aud_tree[2], unpack);
 
                    smk_huff8_lookup(bs, aud_tree[3], unpack2);
 
                    ((short*)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short*)t)[j - 2];
 
                    j++;
 
                    k += 2;
 
                }
 
            }
 
        }
 
 
 
        /* All done with the trees, free them. */
 
        for (j = 0; j < 4; j++) {
 
            if (aud_tree[j]) {
 
                smk_huff8_free(aud_tree[j]);
 
            }
 
        }
 
 
 
        /* free bitstream */
 
        smk_free(bs);
 
    }
 
 
 
    return 0;
 
 
 
error:
 
    /* All done with the trees, free them. */
 
    for (j = 0; j < 4; j++) {
 
        if (aud_tree[j]) {
 
            smk_huff8_free(aud_tree[j]);
 
        }
 
    }
 
 
 
    smk_free(bs);
 
 
 
    return -1;
 
}
 
 
 
/* "Renders" (unpacks) the frame at cur_frame
 
        Preps all the image and audio pointers */
 
static char smk_render(smk s) {
 
    unsigned long i, size;
 
    unsigned char *buffer = NULL, *p, track;
 
 
 
    /* sanity check */
 
    smk_assert(s);
 
 
 
    /* Retrieve current chunk_size for this frame. */
 
    if (!(i = s->chunk_size[s->cur_frame])) {
 
        fprintf(stderr
, "libsmacker::smk_render(s) - Warning: frame %lu: chunk_size is 0.\n", s
->cur_frame
);  
        goto error;
 
    }
 
 
 
    if (s->mode == SMK_MODE_DISK) {
 
        /* Skip to frame in file */
 
        if (fseek(s
->source.
file.
fp, s
->source.
file.
chunk_offset[s
->cur_frame
], SEEK_SET
)) {  
            fprintf(stderr
, "libsmacker::smk_render(s) - ERROR: fseek to frame %lu (offset %lu) failed.\n", s
->cur_frame
, s
->source.
file.
chunk_offset[s
->cur_frame
]);  
            perror("\tError reported was");  
            goto error;
 
        }
 
 
 
        /* In disk-streaming mode: make way for our incoming chunk buffer */
 
        smk_malloc(buffer, i);
 
 
 
        /* Read into buffer */
 
        if (smk_read_file(buffer, s->chunk_size[s->cur_frame], s->source.file.fp) < 0) {
 
            fprintf(stderr
, "libsmacker::smk_render(s) - ERROR: frame %lu (offset %lu): smk_read had errors.\n", s
->cur_frame
, s
->source.
file.
chunk_offset[s
->cur_frame
]);  
            goto error;
 
        }
 
    } else {
 
        /* Just point buffer at the right place */
 
        if (!s->source.chunk_data[s->cur_frame]) {
 
            fprintf(stderr
, "libsmacker::smk_render(s) - ERROR: frame %lu: memory chunk is a NULL pointer.\n", s
->cur_frame
);  
            goto error;
 
        }
 
        buffer = s->source.chunk_data[s->cur_frame];
 
    }
 
 
 
    p = buffer;
 
 
 
    /* Palette record first */
 
    if (s->frame_type[s->cur_frame] & 0x01) {
 
        /* need at least 1 byte to process */
 
        if (!i) {
 
            fprintf(stderr
, "libsmacker::smk_render(s) - ERROR: frame %lu: insufficient data for a palette rec.\n", s
->cur_frame
);  
            goto error;
 
        }
 
 
 
        /* Byte 1 in block, times 4, tells how many
 
                        subsequent bytes are present */
 
        size = 4 * (*p);
 
 
 
        /* If video rendering enabled, kick this off for decode. */
 
        if (s->video.enable) {
 
            smk_render_palette(&(s->video), p + 1, size - 1);
 
        }
 
        p += size;
 
        i -= size;
 
    }
 
 
 
    /* Unpack audio chunks */
 
    for (track = 0; track < 7; track++) {
 
        if (s->frame_type[s->cur_frame] & (0x02 << track)) {
 
            /* need at least 4 byte to process */
 
            if (i < 4) {
 
                fprintf(stderr
, "libsmacker::smk_render(s) - ERROR: frame %lu: insufficient data for audio[%u] rec.\n", s
->cur_frame
, track
);  
                goto error;
 
            }
 
 
 
            /* First 4 bytes in block tell how many
 
                                subsequent bytes are present */
 
            size = (((unsigned int)p[3] << 24) | ((unsigned int)p[2] << 16) | ((unsigned int)p[1] << 8) | ((unsigned int)p[0]));
 
 
 
            /* If audio rendering enabled, kick this off for decode. */
 
            if (s->audio[track].enable) {
 
                smk_render_audio(&s->audio[track], p + 4, size - 4);
 
            }
 
            p += size;
 
            i -= size;
 
        } else {
 
            s->audio[track].buffer_size = 0;
 
        }
 
    }
 
 
 
    /* Unpack video chunk */
 
    if (s->video.enable) {
 
        smk_render_video(&(s->video), p, i);
 
    }
 
 
 
    if (s->mode == SMK_MODE_DISK) {
 
        /* Remember that buffer we allocated?  Trash it */
 
        smk_free(buffer);
 
    }
 
 
 
    return 0;
 
 
 
error:
 
    if (s->mode == SMK_MODE_DISK) {
 
        /* Remember that buffer we allocated?  Trash it */
 
        smk_free(buffer);
 
    }
 
 
 
    return -1;
 
}
 
 
 
/* rewind to first frame and unpack */
 
char smk_first(smk s) {
 
    smk_assert(s);
 
 
 
    s->cur_frame = 0;
 
    if (smk_render(s) < 0) {
 
        fprintf(stderr
, "libsmacker::smk_first(s) - Warning: frame %lu: smk_render returned errors.\n", s
->cur_frame
);  
        goto error;
 
    }
 
 
 
    if (s->f == 1)
 
        return SMK_LAST;
 
    return SMK_MORE;
 
 
 
error:
 
    return -1;
 
}
 
 
 
/* advance to next frame */
 
char smk_next(smk s) {
 
    smk_assert(s);
 
 
 
    if (s->cur_frame + 1 < (s->f + s->ring_frame)) {
 
        s->cur_frame++;
 
        if (smk_render(s) < 0) {
 
            fprintf(stderr
, "libsmacker::smk_next(s) - Warning: frame %lu: smk_render returned errors.\n", s
->cur_frame
);  
            goto error;
 
        }
 
        if (s->cur_frame + 1 == (s->f + s->ring_frame)) {
 
            return SMK_LAST;
 
        }
 
        return SMK_MORE;
 
    } else if (s->ring_frame) {
 
        s->cur_frame = 1;
 
        if (smk_render(s) < 0) {
 
            fprintf(stderr
, "libsmacker::smk_next(s) - Warning: frame %lu: smk_render returned errors.\n", s
->cur_frame
);  
            goto error;
 
        }
 
        if (s->cur_frame + 1 == (s->f + s->ring_frame)) {
 
            return SMK_LAST;
 
        }
 
        return SMK_MORE;
 
    }
 
    return SMK_DONE;
 
 
 
error:
 
    return -1;
 
}
 
 
 
/* seek to a keyframe in an smk */
 
char smk_seek_keyframe(smk s, unsigned long f) {
 
    smk_assert(s);
 
 
 
    /* rewind (or fast forward!) exactly to f */
 
    s->cur_frame = f;
 
 
 
    /* roll back to previous keyframe in stream, or 0 if no keyframes exist */
 
    while (s->cur_frame > 0 && !(s->keyframe[s->cur_frame])) {
 
        s->cur_frame--;
 
    }
 
 
 
    /* render the frame: we're ready */
 
    if (smk_render(s) < 0) {
 
        fprintf(stderr
, "libsmacker::smk_seek_keyframe(s,%lu) - Warning: frame %lu: smk_render returned errors.\n", f
, s
->cur_frame
);  
        goto error;
 
    }
 
 
 
    return 0;
 
 
 
error:
 
    return -1;
 
}