mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 19:55:23 +00:00
a8784ee524
Reorganized MVE to use own MVESTREAM pointer for each opened MVE file.
481 lines
11 KiB
C++
481 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2002-2024 D2X Project
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <cstring> // for mem* functions
|
|
|
|
#include "mvelib.h"
|
|
|
|
static const char MVE_HEADER[] = "Interplay MVE File\x1A";
|
|
static const short MVE_HDRCONST1 = 0x001A;
|
|
static const short MVE_HDRCONST2 = 0x0100;
|
|
static const short MVE_HDRCONST3 = 0x1133;
|
|
|
|
mve_cb_Read mve_read;
|
|
mve_cb_Alloc mve_alloc;
|
|
mve_cb_Free mve_free;
|
|
mve_cb_ShowFrame mve_showframe;
|
|
mve_cb_SetPalette mve_setpalette;
|
|
|
|
/*
|
|
* private utility functions
|
|
*/
|
|
static short _mve_get_short(unsigned char *data);
|
|
static unsigned short _mve_get_ushort(unsigned char *data);
|
|
|
|
/*
|
|
* private functions for mvefile
|
|
*/
|
|
static MVEFILE *_mvefile_alloc();
|
|
static void _mvefile_free(MVEFILE *movie);
|
|
static int _mvefile_open(MVEFILE *movie, void *stream);
|
|
static void _mvefile_reset(MVEFILE *movie);
|
|
static int _mvefile_read_header(MVEFILE *movie);
|
|
static void _mvefile_set_buffer_size(MVEFILE *movie, int buf_size);
|
|
static int _mvefile_fetch_next_chunk(MVEFILE *movie);
|
|
|
|
/*
|
|
* private functions for mvestream
|
|
*/
|
|
static MVESTREAM *_mvestream_alloc();
|
|
static void _mvestream_free(MVESTREAM *movie);
|
|
static int _mvestream_open(MVESTREAM *movie, void *stream);
|
|
static void _mvestream_reset(MVESTREAM *movie);
|
|
|
|
/************************************************************
|
|
* public MVEFILE functions
|
|
************************************************************/
|
|
|
|
/*
|
|
* open an MVE file
|
|
*/
|
|
MVEFILE *mvefile_open(void *stream) {
|
|
MVEFILE *file;
|
|
|
|
/* create the file */
|
|
file = _mvefile_alloc();
|
|
if (!_mvefile_open(file, stream)) {
|
|
_mvefile_free(file);
|
|
return nullptr;
|
|
}
|
|
|
|
/* initialize the file */
|
|
_mvefile_set_buffer_size(file, 1024);
|
|
|
|
/* verify the file's header */
|
|
if (!_mvefile_read_header(file)) {
|
|
_mvefile_free(file);
|
|
return nullptr;
|
|
}
|
|
|
|
/* now, prefetch the next chunk */
|
|
_mvefile_fetch_next_chunk(file);
|
|
|
|
return file;
|
|
}
|
|
|
|
/*
|
|
* close a MVE file
|
|
*/
|
|
void mvefile_close(MVEFILE *movie) { _mvefile_free(movie); }
|
|
|
|
/*
|
|
* reset a MVE file
|
|
*/
|
|
void mvefile_reset(MVEFILE *file) {
|
|
_mvefile_reset(file);
|
|
|
|
/* initialize the file */
|
|
_mvefile_set_buffer_size(file, 1024);
|
|
|
|
/* verify the file's header */
|
|
if (!_mvefile_read_header(file)) {
|
|
_mvefile_free(file);
|
|
// return NULL;
|
|
}
|
|
|
|
/* now, prefetch the next chunk */
|
|
_mvefile_fetch_next_chunk(file);
|
|
}
|
|
|
|
/*
|
|
* get the size of the next segment
|
|
*/
|
|
int mvefile_get_next_segment_size(MVEFILE *movie) {
|
|
/* if nothing is cached, fail */
|
|
if (movie->cur_chunk == nullptr || movie->next_segment >= movie->cur_fill)
|
|
return -1;
|
|
|
|
/* if we don't have enough data to get a segment, fail */
|
|
if (movie->cur_fill - movie->next_segment < 4)
|
|
return -1;
|
|
|
|
/* otherwise, get the data length */
|
|
return _mve_get_short(movie->cur_chunk + movie->next_segment);
|
|
}
|
|
|
|
/*
|
|
* get type of next segment in chunk (0xff if no more segments in chunk)
|
|
*/
|
|
unsigned char mvefile_get_next_segment_major(MVEFILE *movie) {
|
|
/* if nothing is cached, fail */
|
|
if (movie->cur_chunk == nullptr || movie->next_segment >= movie->cur_fill)
|
|
return 0xff;
|
|
|
|
/* if we don't have enough data to get a segment, fail */
|
|
if (movie->cur_fill - movie->next_segment < 4)
|
|
return 0xff;
|
|
|
|
/* otherwise, get the data length */
|
|
return movie->cur_chunk[movie->next_segment + 2];
|
|
}
|
|
|
|
/*
|
|
* get subtype (version) of next segment in chunk (0xff if no more segments in
|
|
* chunk)
|
|
*/
|
|
unsigned char mvefile_get_next_segment_minor(MVEFILE *movie) {
|
|
/* if nothing is cached, fail */
|
|
if (movie->cur_chunk == nullptr || movie->next_segment >= movie->cur_fill)
|
|
return 0xff;
|
|
|
|
/* if we don't have enough data to get a segment, fail */
|
|
if (movie->cur_fill - movie->next_segment < 4)
|
|
return 0xff;
|
|
|
|
/* otherwise, get the data length */
|
|
return movie->cur_chunk[movie->next_segment + 3];
|
|
}
|
|
|
|
/*
|
|
* see next segment (return NULL if no next segment)
|
|
*/
|
|
unsigned char *mvefile_get_next_segment(MVEFILE *movie) {
|
|
/* if nothing is cached, fail */
|
|
if (movie->cur_chunk == nullptr || movie->next_segment >= movie->cur_fill)
|
|
return nullptr;
|
|
|
|
/* if we don't have enough data to get a segment, fail */
|
|
if (movie->cur_fill - movie->next_segment < 4)
|
|
return nullptr;
|
|
|
|
/* otherwise, get the data length */
|
|
return movie->cur_chunk + movie->next_segment + 4;
|
|
}
|
|
|
|
/*
|
|
* advance to next segment
|
|
*/
|
|
void mvefile_advance_segment(MVEFILE *movie) {
|
|
/* if nothing is cached, fail */
|
|
if (movie->cur_chunk == nullptr || movie->next_segment >= movie->cur_fill)
|
|
return;
|
|
|
|
/* if we don't have enough data to get a segment, fail */
|
|
if (movie->cur_fill - movie->next_segment < 4)
|
|
return;
|
|
|
|
/* else, advance to next segment */
|
|
movie->next_segment += (4 + _mve_get_ushort(movie->cur_chunk + movie->next_segment));
|
|
}
|
|
|
|
/*
|
|
* fetch the next chunk (return 0 if at end of stream)
|
|
*/
|
|
int mvefile_fetch_next_chunk(MVEFILE *movie) { return _mvefile_fetch_next_chunk(movie); }
|
|
|
|
/************************************************************
|
|
* public MVESTREAM functions
|
|
************************************************************/
|
|
|
|
/*
|
|
* open an MVE stream
|
|
*/
|
|
MVESTREAM *mve_open(void *stream) {
|
|
MVESTREAM *movie;
|
|
|
|
/* allocate */
|
|
movie = _mvestream_alloc();
|
|
|
|
/* open */
|
|
if (!_mvestream_open(movie, stream)) {
|
|
_mvestream_free(movie);
|
|
return nullptr;
|
|
}
|
|
|
|
return movie;
|
|
}
|
|
|
|
/*
|
|
* close an MVE stream
|
|
*/
|
|
void mve_close(MVESTREAM *movie) { _mvestream_free(movie); }
|
|
|
|
/*
|
|
* reset an MVE stream
|
|
*/
|
|
void mve_reset(MVESTREAM *movie) { _mvestream_reset(movie); }
|
|
|
|
/*
|
|
* set segment type handler
|
|
*/
|
|
void mve_set_handler(MVESTREAM *movie, unsigned char major, MVESEGMENTHANDLER handler) {
|
|
if (major < 32)
|
|
movie->handlers[major] = handler;
|
|
}
|
|
|
|
/*
|
|
* set segment handler context
|
|
*/
|
|
void mve_set_handler_context(MVESTREAM *movie, void *context) { movie->context = context; }
|
|
|
|
/*
|
|
* play next chunk
|
|
*/
|
|
int mve_play_next_chunk(MVESTREAM *movie) {
|
|
unsigned char major, minor;
|
|
unsigned char *data;
|
|
int len;
|
|
|
|
/* loop over segments */
|
|
major = mvefile_get_next_segment_major(movie->movie);
|
|
while (major != 0xff) {
|
|
/* check whether to handle the segment */
|
|
if (major < 32 && movie->handlers[major] != nullptr) {
|
|
minor = mvefile_get_next_segment_minor(movie->movie);
|
|
len = mvefile_get_next_segment_size(movie->movie);
|
|
data = mvefile_get_next_segment(movie->movie);
|
|
|
|
if (!movie->handlers[major](major, minor, data, len, movie->context))
|
|
return 0;
|
|
}
|
|
|
|
/* advance to next segment */
|
|
mvefile_advance_segment(movie->movie);
|
|
major = mvefile_get_next_segment_major(movie->movie);
|
|
}
|
|
|
|
if (!mvefile_fetch_next_chunk(movie->movie))
|
|
return 0;
|
|
|
|
/* return status */
|
|
return 1;
|
|
}
|
|
|
|
/************************************************************
|
|
* private functions
|
|
************************************************************/
|
|
|
|
/*
|
|
* allocate an MVEFILE
|
|
*/
|
|
static MVEFILE *_mvefile_alloc() {
|
|
MVEFILE *file = (MVEFILE *)mve_alloc(sizeof(MVEFILE));
|
|
file->stream = nullptr;
|
|
file->cur_chunk = nullptr;
|
|
file->buf_size = 0;
|
|
file->cur_fill = 0;
|
|
file->next_segment = 0;
|
|
|
|
return file;
|
|
}
|
|
|
|
/*
|
|
* free an MVE file
|
|
*/
|
|
static void _mvefile_free(MVEFILE *movie) {
|
|
/* free the stream */
|
|
movie->stream = nullptr;
|
|
|
|
/* free the buffer */
|
|
if (movie->cur_chunk)
|
|
mve_free(movie->cur_chunk);
|
|
movie->cur_chunk = nullptr;
|
|
|
|
/* not strictly necessary */
|
|
movie->buf_size = 0;
|
|
movie->cur_fill = 0;
|
|
movie->next_segment = 0;
|
|
|
|
/* free the struct */
|
|
mve_free(movie);
|
|
}
|
|
|
|
/*
|
|
* open the file stream in thie object
|
|
*/
|
|
static int _mvefile_open(MVEFILE *file, void *stream) {
|
|
file->stream = stream;
|
|
if (!file->stream)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* allocate an MVEFILE
|
|
*/
|
|
static void _mvefile_reset(MVEFILE *file) {
|
|
fseek((FILE *)file->stream, 0, SEEK_SET);
|
|
file->cur_chunk = nullptr;
|
|
file->buf_size = 0;
|
|
file->cur_fill = 0;
|
|
file->next_segment = 0;
|
|
}
|
|
|
|
/*
|
|
* read and verify the header of the recently opened file
|
|
*/
|
|
static int _mvefile_read_header(MVEFILE *movie) {
|
|
unsigned char buffer[26];
|
|
|
|
/* check the file is open */
|
|
if (!movie->stream)
|
|
return 0;
|
|
|
|
/* check the file is long enough */
|
|
if (!mve_read(movie->stream, buffer, 26))
|
|
return 0;
|
|
|
|
/* check the signature */
|
|
if (memcmp(buffer, MVE_HEADER, 20))
|
|
return 0;
|
|
|
|
/* check the hard-coded constants */
|
|
if (_mve_get_short(buffer + 20) != MVE_HDRCONST1)
|
|
return 0;
|
|
if (_mve_get_short(buffer + 22) != MVE_HDRCONST2)
|
|
return 0;
|
|
if (_mve_get_short(buffer + 24) != MVE_HDRCONST3)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void _mvefile_set_buffer_size(MVEFILE *movie, int buf_size) {
|
|
unsigned char *new_buffer;
|
|
int new_len;
|
|
|
|
/* check if this would be a redundant operation */
|
|
if (buf_size <= movie->buf_size)
|
|
return;
|
|
|
|
/* allocate new buffer */
|
|
new_len = 100 + buf_size;
|
|
new_buffer = (unsigned char *)mve_alloc(new_len);
|
|
|
|
/* copy old data */
|
|
if (movie->cur_chunk && movie->cur_fill)
|
|
memcpy(new_buffer, movie->cur_chunk, movie->cur_fill);
|
|
|
|
/* free old buffer */
|
|
if (movie->cur_chunk) {
|
|
mve_free(movie->cur_chunk);
|
|
movie->cur_chunk = 0;
|
|
}
|
|
|
|
/* install new buffer */
|
|
movie->cur_chunk = new_buffer;
|
|
movie->buf_size = new_len;
|
|
}
|
|
|
|
static int _mvefile_fetch_next_chunk(MVEFILE *movie) {
|
|
unsigned char buffer[4];
|
|
unsigned short length;
|
|
|
|
/* fail if not open */
|
|
if (!movie->stream)
|
|
return 0;
|
|
|
|
/* fail if we can't read the next segment descriptor */
|
|
if (!mve_read(movie->stream, buffer, 4))
|
|
return 0;
|
|
|
|
/* pull out the next length */
|
|
length = _mve_get_short(buffer);
|
|
|
|
/* make sure we've got sufficient space */
|
|
_mvefile_set_buffer_size(movie, length);
|
|
|
|
/* read the chunk */
|
|
if (!mve_read(movie->stream, movie->cur_chunk, length))
|
|
return 0;
|
|
movie->cur_fill = length;
|
|
movie->next_segment = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static short _mve_get_short(unsigned char *data) {
|
|
short value;
|
|
value = data[0] | (data[1] << 8);
|
|
return value;
|
|
}
|
|
|
|
static unsigned short _mve_get_ushort(unsigned char *data) {
|
|
unsigned short value;
|
|
value = data[0] | (data[1] << 8);
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* allocate an MVESTREAM
|
|
*/
|
|
static MVESTREAM *_mvestream_alloc() {
|
|
MVESTREAM *movie;
|
|
|
|
/* allocate and zero-initialize everything */
|
|
movie = (MVESTREAM *)mve_alloc(sizeof(MVESTREAM));
|
|
movie->movie = nullptr;
|
|
movie->context = 0;
|
|
memset(movie->handlers, 0, sizeof(movie->handlers));
|
|
|
|
return movie;
|
|
}
|
|
|
|
/*
|
|
* free an MVESTREAM
|
|
*/
|
|
static void _mvestream_free(MVESTREAM *movie) {
|
|
/* close MVEFILE */
|
|
if (movie == nullptr)
|
|
return;
|
|
if (movie->movie != nullptr)
|
|
mvefile_close(movie->movie);
|
|
movie->movie = nullptr;
|
|
|
|
/* clear context and handlers */
|
|
movie->context = nullptr;
|
|
memset(movie->handlers, 0, sizeof(movie->handlers));
|
|
|
|
/* free the struct */
|
|
mve_free(movie);
|
|
}
|
|
|
|
/*
|
|
* open an MVESTREAM object
|
|
*/
|
|
static int _mvestream_open(MVESTREAM *movie, void *stream) {
|
|
movie->movie = mvefile_open(stream);
|
|
|
|
return (movie->movie == nullptr) ? 0 : 1;
|
|
}
|
|
|
|
/*
|
|
* reset an MVESTREAM
|
|
*/
|
|
static void _mvestream_reset(MVESTREAM *movie) { mvefile_reset(movie->movie); }
|