mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-16 06:39:17 +00:00
788 lines
20 KiB
C++
788 lines
20 KiB
C++
/* Residual - A 3D game interpreter
|
|
*
|
|
* Residual is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
*/
|
|
|
|
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
|
|
#define FORBIDDEN_SYMBOL_EXCEPTION_chdir
|
|
#define FORBIDDEN_SYMBOL_EXCEPTION_getcwd
|
|
#define FORBIDDEN_SYMBOL_EXCEPTION_getwd
|
|
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
|
|
#define FORBIDDEN_SYMBOL_EXCEPTION_unlink
|
|
|
|
#include "common/endian.h"
|
|
#include "common/timer.h"
|
|
#include "common/file.h"
|
|
#include "common/events.h"
|
|
#include "common/system.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/mixer.h"
|
|
#include "audio/decoders/raw.h"
|
|
|
|
#include "engines/grim/movie/smush.h"
|
|
|
|
#include "engines/grim/grim.h"
|
|
#include "engines/grim/colormap.h"
|
|
#include "engines/grim/resource.h"
|
|
#include "engines/grim/savegame.h"
|
|
|
|
namespace Grim {
|
|
|
|
#define SMUSH_ALTSPEED(x) (x & 0x000004)
|
|
|
|
#define ANNO_HEADER "MakeAnim animation type 'Bl16' parameters: "
|
|
#define BUFFER_SIZE 16385
|
|
|
|
static uint16 smushDestTable[5786];
|
|
|
|
MoviePlayer *CreateSmushPlayer() {
|
|
return new SmushPlayer();
|
|
}
|
|
|
|
void SmushPlayer::timerCallback(void *) {
|
|
if (g_grim->getGameFlags() & ADGF_DEMO)
|
|
((SmushPlayer *)g_movie)->handleFrameDemo();
|
|
else
|
|
((SmushPlayer *)g_movie)->handleFrame();
|
|
}
|
|
|
|
SmushPlayer::SmushPlayer() {
|
|
g_movie = this;
|
|
_IACTpos = 0;
|
|
_nbframes = 0;
|
|
}
|
|
|
|
SmushPlayer::~SmushPlayer() {
|
|
deinit();
|
|
}
|
|
|
|
void SmushPlayer::init() {
|
|
_IACTpos = 0;
|
|
_stream = NULL;
|
|
_frame = 0;
|
|
_movieTime = 0;
|
|
_videoFinished = false;
|
|
_videoPause = false;
|
|
_updateNeeded = false;
|
|
|
|
assert(!_internalBuffer);
|
|
assert(!_externalBuffer);
|
|
|
|
if (!(g_grim->getGameFlags() & ADGF_DEMO)) {
|
|
_internalBuffer = new byte[_width * _height * 2];
|
|
_externalBuffer = new byte[_width * _height * 2];
|
|
vimaInit(smushDestTable);
|
|
}
|
|
g_system->getTimerManager()->installTimerProc(&timerCallback, _speed, NULL);
|
|
}
|
|
|
|
void SmushPlayer::deinit() {
|
|
g_system->getTimerManager()->removeTimerProc(&timerCallback);
|
|
|
|
if (_internalBuffer) {
|
|
delete[] _internalBuffer;
|
|
_internalBuffer = NULL;
|
|
}
|
|
if (_externalBuffer) {
|
|
delete[] _externalBuffer;
|
|
_externalBuffer = NULL;
|
|
}
|
|
if (_videoLooping && _startPos) {
|
|
delete[] _startPos->tmpBuf;
|
|
delete[] _startPos;
|
|
_startPos = NULL;
|
|
}
|
|
if (_stream) {
|
|
_stream->finish();
|
|
_stream = NULL;
|
|
g_system->getMixer()->stopHandle(_soundHandle);
|
|
}
|
|
_videoLooping = false;
|
|
_videoFinished = true;
|
|
_videoPause = true;
|
|
if (g_grim->getGameFlags() & ADGF_DEMO)
|
|
_f.close();
|
|
else
|
|
_file.close();
|
|
}
|
|
|
|
void SmushPlayer::handleWave(const byte *src, uint32 size) {
|
|
int16 *dst = (int16 *) malloc(size * _channels * sizeof(int16));
|
|
decompressVima(src, dst, size * _channels * 2, smushDestTable);
|
|
|
|
int flags = Audio::FLAG_16BITS;
|
|
if (_channels == 2)
|
|
flags |= Audio::FLAG_STEREO;
|
|
|
|
if (!_stream) {
|
|
_stream = Audio::makeQueuingAudioStream(_freq, (_channels == 2));
|
|
g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, _stream);
|
|
}
|
|
if (g_system->getMixer()->isReady()) {
|
|
_stream->queueBuffer((byte *)dst, size * _channels * 2, DisposeAfterUse::YES, flags);
|
|
} else {
|
|
free(dst);
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleFrame() {
|
|
uint32 tag;
|
|
int32 size;
|
|
int pos = 0;
|
|
|
|
if (_videoPause)
|
|
return;
|
|
|
|
if (_videoFinished) {
|
|
_videoPause = true;
|
|
return;
|
|
}
|
|
|
|
if (_frame == _nbframes) {
|
|
// If the video has been looping and was previously on the last
|
|
// frame then reset the frame number and the movie time, this
|
|
// needs to occur at the beginning so the last frame has time to
|
|
// render appropriately
|
|
_frame = 1;
|
|
_movieTime = 0;
|
|
}
|
|
|
|
tag = _file.readUint32BE();
|
|
if (tag == MKTAG('A','N','N','O')) {
|
|
char *anno;
|
|
byte *data;
|
|
|
|
size = _file.readUint32BE();
|
|
data = new byte[size];
|
|
_file.read(data, size);
|
|
anno = (char *)data;
|
|
if (strncmp(anno, ANNO_HEADER, sizeof(ANNO_HEADER) - 1) == 0) {
|
|
//char *annoData = anno + sizeof(ANNO_HEADER);
|
|
|
|
// Examples:
|
|
// Water streaming around boat from Manny's balcony
|
|
// MakeAnim animation type 'Bl16' parameters: 10000;12000;100;1;0;0;0;0;25;0;
|
|
// Water in front of the Blue Casket
|
|
// MakeAnim animation type 'Bl16' parameters: 20000;25000;100;1;0;0;0;0;25;0;
|
|
// Scrimshaw exterior:
|
|
// MakeAnim animation type 'Bl16' parameters: 6000;8000;100;0;0;0;0;0;2;0;
|
|
// Lola engine room (loops a limited number of times?):
|
|
// MakeAnim animation type 'Bl16' parameters: 6000;8000;90;1;0;0;0;0;2;0;
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
|
|
debug("Announcement data: %s\n", anno);
|
|
// It looks like the announcement data is actually for setting some of the
|
|
// header parameters, not for any looping purpose
|
|
} else {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
|
|
debug("Announcement header not understood: %s\n", anno);
|
|
}
|
|
delete[] anno;
|
|
tag = _file.readUint32BE();
|
|
}
|
|
|
|
assert(tag == MKTAG('F','R','M','E'));
|
|
size = _file.readUint32BE();
|
|
byte *frame = new byte[size];
|
|
_file.read(frame, size);
|
|
|
|
do {
|
|
if (READ_BE_UINT32(frame + pos) == MKTAG('B','l','1','6')) {
|
|
_blocky16.decode(_internalBuffer, frame + pos + 8);
|
|
pos += READ_BE_UINT32(frame + pos + 4) + 8;
|
|
} else if (READ_BE_UINT32(frame + pos) == MKTAG('W','a','v','e')) {
|
|
int decompressed_size = READ_BE_UINT32(frame + pos + 8);
|
|
if (decompressed_size < 0)
|
|
handleWave(frame + pos + 8 + 4 + 8, READ_BE_UINT32(frame + pos + 8 + 8));
|
|
else
|
|
handleWave(frame + pos + 8 + 4, decompressed_size);
|
|
pos += READ_BE_UINT32(frame + pos + 4) + 8;
|
|
} else if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_ERROR || gDebugLevel == DEBUG_ALL) {
|
|
error("SmushPlayer::handleFrame() unknown tag");
|
|
}
|
|
} while (pos < size);
|
|
delete[] frame;
|
|
|
|
memcpy(_externalBuffer, _internalBuffer, _width * _height * 2);
|
|
_updateNeeded = true;
|
|
|
|
_frame++;
|
|
_movieTime += _speed / 1000;
|
|
if (_frame == _nbframes) {
|
|
// If we're not supposed to loop (or looping fails) then end the video
|
|
if (!_videoLooping || !_file.setPos(_startPos)) {
|
|
_videoFinished = true;
|
|
g_grim->setMode(ENGINE_MODE_NORMAL);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static byte delta_color(byte org_color, int16 delta_color) {
|
|
int t = (org_color * 129 + delta_color) / 128;
|
|
return CLIP(t, 0, 255);
|
|
}
|
|
|
|
void SmushPlayer::handleDeltaPalette(byte *src, int32 size) {
|
|
if (size == 0x300 * 3 + 4) {
|
|
for (int i = 0; i < 0x300; i++)
|
|
_deltaPal[i] = READ_LE_UINT16(src + (i * 2) + 4);
|
|
memcpy(_pal, src + 0x600 + 4, 0x300);
|
|
} else if (size == 6) {
|
|
for (int i = 0; i < 0x300; i++)
|
|
_pal[i] = delta_color(_pal[i], _deltaPal[i]);
|
|
} else {
|
|
error("SmushPlayer::handleDeltaPalette() Wrong size for DeltaPalette");
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleIACT(const byte *src, int32 size) {
|
|
int32 bsize = size - 18;
|
|
const byte *d_src = src + 18;
|
|
|
|
while (bsize > 0) {
|
|
if (_IACTpos >= 2) {
|
|
int32 len = READ_BE_UINT16(_IACToutput) + 2;
|
|
len -= _IACTpos;
|
|
if (len > bsize) {
|
|
memcpy(_IACToutput + _IACTpos, d_src, bsize);
|
|
_IACTpos += bsize;
|
|
bsize = 0;
|
|
} else {
|
|
byte *output_data = new byte[4096];
|
|
memcpy(_IACToutput + _IACTpos, d_src, len);
|
|
byte *dst = output_data;
|
|
byte *d_src2 = _IACToutput;
|
|
d_src2 += 2;
|
|
int32 count = 1024;
|
|
byte variable1 = *d_src2++;
|
|
byte variable2 = variable1 / 16;
|
|
variable1 &= 0x0f;
|
|
do {
|
|
byte value;
|
|
value = *(d_src2++);
|
|
if (value == 0x80) {
|
|
*dst++ = *d_src2++;
|
|
*dst++ = *d_src2++;
|
|
} else {
|
|
int16 val = (int8)value << variable2;
|
|
*dst++ = val >> 8;
|
|
*dst++ = (byte)(val);
|
|
}
|
|
value = *(d_src2++);
|
|
if (value == 0x80) {
|
|
*dst++ = *d_src2++;
|
|
*dst++ = *d_src2++;
|
|
} else {
|
|
int16 val = (int8)value << variable1;
|
|
*dst++ = val >> 8;
|
|
*dst++ = (byte)(val);
|
|
}
|
|
} while (--count);
|
|
|
|
if (!_stream) {
|
|
_stream = Audio::makeQueuingAudioStream(22050, true);
|
|
g_system->getMixer()->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _stream);
|
|
}
|
|
_stream->queueBuffer(output_data, 0x1000, DisposeAfterUse::YES, Audio::FLAG_STEREO | Audio::FLAG_16BITS);
|
|
|
|
bsize -= len;
|
|
d_src += len;
|
|
_IACTpos = 0;
|
|
}
|
|
} else {
|
|
if (bsize > 1 && _IACTpos == 0) {
|
|
*(_IACToutput + 0) = *d_src++;
|
|
_IACTpos = 1;
|
|
bsize--;
|
|
}
|
|
*(_IACToutput + _IACTpos) = *d_src++;
|
|
_IACTpos++;
|
|
bsize--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleFrameDemo() {
|
|
uint32 tag;
|
|
int32 size;
|
|
int pos = 0;
|
|
|
|
if (_videoPause)
|
|
return;
|
|
|
|
if (_videoFinished) {
|
|
_videoPause = true;
|
|
return;
|
|
}
|
|
|
|
tag = _f.readUint32BE();
|
|
assert(tag == MKTAG('F','R','M','E'));
|
|
size = _f.readUint32BE();
|
|
byte *frame = new byte[size];
|
|
_f.read(frame, size);
|
|
|
|
do {
|
|
if (READ_BE_UINT32(frame + pos) == MKTAG('F','O','B','J')) {
|
|
_x = READ_LE_UINT16(frame + pos + 10);
|
|
_y = READ_LE_UINT16(frame + pos + 12);
|
|
int width = READ_LE_UINT16(frame + pos + 14);
|
|
int height = READ_LE_UINT16(frame + pos + 16);
|
|
if (width != _width || height != _height) {
|
|
delete[] _internalBuffer;
|
|
delete[] _externalBuffer;
|
|
_width = width;
|
|
_height = height;
|
|
_internalBuffer = new byte[_width * _height];
|
|
_externalBuffer = new byte[_width * _height * 2];
|
|
_blocky8.init(_width, _height);
|
|
}
|
|
_blocky8.decode(_internalBuffer, frame + pos + 8 + 14);
|
|
pos += READ_BE_UINT32(frame + pos + 4) + 8;
|
|
} else if (READ_BE_UINT32(frame + pos) == MKTAG('I','A','C','T')) {
|
|
handleIACT(frame + pos + 8, READ_BE_UINT32(frame + pos + 4));
|
|
int offset = READ_BE_UINT32(frame + pos + 4) + 8;
|
|
if (offset & 1)
|
|
offset += 1;
|
|
pos += offset;
|
|
} else if (READ_BE_UINT32(frame + pos) == MKTAG('X','P','A','L')) {
|
|
handleDeltaPalette(frame + pos + 8, READ_BE_UINT32(frame + pos + 4));
|
|
pos += READ_BE_UINT32(frame + pos + 4) + 8;
|
|
} else {
|
|
error("SmushPlayer::handleFrame() unknown tag");
|
|
}
|
|
} while (pos < size);
|
|
delete[] frame;
|
|
|
|
uint16 *d = (uint16 *)_externalBuffer;
|
|
for (int l = 0; l < _width * _height; l++) {
|
|
int index = _internalBuffer[l];
|
|
d[l] = ((_pal[(index * 3) + 0] & 0xF8) << 8) | ((_pal[(index * 3) + 1] & 0xFC) << 3) | (_pal[(index * 3) + 2] >> 3);
|
|
}
|
|
|
|
_updateNeeded = true;
|
|
|
|
_frame++;
|
|
_movieTime += _speed / 1000;
|
|
if (_frame == _nbframes) {
|
|
_videoFinished = true;
|
|
g_grim->setMode(ENGINE_MODE_NORMAL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleFramesHeader() {
|
|
uint32 tag;
|
|
int32 size;
|
|
int pos = 0;
|
|
|
|
tag = _file.readUint32BE();
|
|
assert(tag == MKTAG('F','L','H','D'));
|
|
size = _file.readUint32BE();
|
|
byte *f_header = new byte[size];
|
|
_file.read(f_header, size);
|
|
|
|
do {
|
|
if (READ_BE_UINT32(f_header + pos) == MKTAG('B','l','1','6')) {
|
|
pos += READ_BE_UINT32(f_header + pos + 4) + 8;
|
|
} else if (READ_BE_UINT32(f_header + pos) == MKTAG('W','a','v','e')) {
|
|
_freq = READ_LE_UINT32(f_header + pos + 8);
|
|
_channels = READ_LE_UINT32(f_header + pos + 12);
|
|
pos += 20;
|
|
} else {
|
|
error("SmushPlayer::handleFramesHeader() unknown tag");
|
|
}
|
|
} while (pos < size);
|
|
delete[] f_header;
|
|
}
|
|
|
|
bool SmushPlayer::setupAnimDemo(const char *file) {
|
|
uint32 tag;
|
|
int32 size;
|
|
|
|
if (!_f.open(file))
|
|
return false;
|
|
|
|
tag = _f.readUint32BE();
|
|
assert(tag == MKTAG('A','N','I','M'));
|
|
size = _f.readUint32BE();
|
|
|
|
tag = _f.readUint32BE();
|
|
assert(tag == MKTAG('A','H','D','R'));
|
|
size = _f.readUint32BE();
|
|
|
|
_f.readUint16BE(); // version
|
|
_nbframes = _f.readUint16LE();
|
|
_f.readUint16BE(); // unknown
|
|
|
|
for (int l = 0; l < 0x300; l++) {
|
|
_pal[l] = _f.readByte();
|
|
}
|
|
_f.readUint32BE();
|
|
_f.readUint32BE();
|
|
_f.readUint32BE();
|
|
_f.readUint32BE();
|
|
_f.readUint32BE();
|
|
|
|
_x = -1;
|
|
_y = -1;
|
|
_width = -1;
|
|
_height = -1;
|
|
_videoLooping = false;
|
|
_startPos = NULL;
|
|
_speed = 66667;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SmushPlayer::setupAnim(const char *file, bool looping, int x, int y) {
|
|
uint32 tag;
|
|
int32 size;
|
|
int16 flags;
|
|
|
|
if (!_file.open(file))
|
|
return false;
|
|
|
|
tag = _file.readUint32BE();
|
|
assert(tag == MKTAG('S','A','N','M'));
|
|
size = _file.readUint32BE();
|
|
|
|
tag = _file.readUint32BE();
|
|
assert(tag == MKTAG('S','H','D','R'));
|
|
size = _file.readUint32BE();
|
|
byte *s_header = new byte[size];
|
|
_file.read(s_header, size);
|
|
_nbframes = READ_LE_UINT32(s_header + 2);
|
|
int width = READ_LE_UINT16(s_header + 8);
|
|
int height = READ_LE_UINT16(s_header + 10);
|
|
if (_width != width || _height != height) {
|
|
_blocky16.init(width, height);
|
|
}
|
|
|
|
_x = x;
|
|
_y = y;
|
|
_width = width;
|
|
_height = height;
|
|
|
|
_speed = READ_LE_UINT32(s_header + 14);
|
|
flags = READ_LE_UINT16(s_header + 18);
|
|
// Output information for checking out the flags
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL) {
|
|
printf("SMUSH Flags:");
|
|
for (int i = 0; i < 16; i++)
|
|
printf(" %d", (flags & (1 << i)) != 0);
|
|
printf("\n");
|
|
}
|
|
// Videos "copaldie.snm" and "getshcks.snm" seem to have
|
|
// the wrong speed value, the value 66667 (used by the
|
|
// other videos) seems to work whereas "2x" (68928)
|
|
// does not quite do it.
|
|
// TODO: Find out what needs to go on here.
|
|
if (SMUSH_ALTSPEED(flags)) {
|
|
printf("Bad time: %d, suggested: %d\n", (int)_speed, (int)(2 * _speed));
|
|
_speed = 66667;
|
|
}
|
|
_videoLooping = looping;
|
|
_startPos = NULL; // Set later
|
|
delete[] s_header;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SmushPlayer::stop() {
|
|
deinit();
|
|
g_grim->setMode(ENGINE_MODE_NORMAL);
|
|
}
|
|
|
|
bool SmushPlayer::play(const char *filename, bool looping, int x, int y) {
|
|
deinit();
|
|
_fname = filename;
|
|
|
|
if (gDebugLevel == DEBUG_SMUSH)
|
|
printf("Playing video '%s'.\n", filename);
|
|
|
|
// Load the video
|
|
if (g_grim->getGameFlags() & ADGF_DEMO) {
|
|
if (!setupAnimDemo(filename))
|
|
return false;
|
|
} else {
|
|
if (!setupAnim(filename, looping, x, y))
|
|
return false;
|
|
|
|
handleFramesHeader();
|
|
}
|
|
|
|
if (_videoLooping)
|
|
_startPos = _file.getPos();
|
|
|
|
init();
|
|
|
|
return true;
|
|
}
|
|
|
|
void SmushPlayer::saveState(SaveGame *state) {
|
|
state->beginSection('SMUS');
|
|
|
|
state->writeString(_fname);
|
|
|
|
state->writeLESint32(_frame);
|
|
state->writeLESint32(_movieTime);
|
|
state->writeLESint32(_videoFinished);
|
|
state->writeLESint32(_videoLooping);
|
|
|
|
state->writeLESint32(_x);
|
|
state->writeLESint32(_y);
|
|
|
|
state->endSection();
|
|
}
|
|
|
|
void SmushPlayer::restoreState(SaveGame *state) {
|
|
state->beginSection('SMUS');
|
|
|
|
_fname = state->readString();
|
|
|
|
int32 frame = state->readLESint32();
|
|
int32 movieTime = state->readLESint32();
|
|
bool videoFinished = state->readLESint32();
|
|
bool videoLooping = state->readLESint32();
|
|
|
|
int x = state->readLESint32();
|
|
int y = state->readLESint32();
|
|
|
|
if (!videoFinished) {
|
|
play(_fname.c_str(), videoLooping, x, y);
|
|
}
|
|
_frame = frame;
|
|
_movieTime = movieTime;
|
|
|
|
state->endSection();
|
|
}
|
|
|
|
zlibFile::zlibFile() {
|
|
_handle = NULL;
|
|
_inBuf = NULL;
|
|
}
|
|
|
|
zlibFile::~zlibFile() {
|
|
close();
|
|
}
|
|
|
|
struct SavePos *zlibFile::getPos() {
|
|
struct SavePos *pos;
|
|
uint32 position = _handle->pos();
|
|
|
|
if (position == ((uint32) -1)) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
|
|
warning("zlibFile::open() unable to find start position");
|
|
return NULL;
|
|
}
|
|
pos = new SavePos;
|
|
pos->filePos = position;
|
|
inflateCopy(&pos->streamBuf, &_stream);
|
|
pos->tmpBuf = new byte[BUFFER_SIZE];
|
|
memcpy(pos->tmpBuf, _inBuf, BUFFER_SIZE);
|
|
return pos;
|
|
}
|
|
|
|
bool zlibFile::setPos(struct SavePos *pos) {
|
|
if (!pos) {
|
|
warning("Unable to rewind SMUSH movie (no position passed)");
|
|
return false;
|
|
}
|
|
if (!_handle || !_handle->isOpen()) {
|
|
warning("Unable to rewind SMUSH movie (invalid handle)");
|
|
return false;
|
|
}
|
|
_handle->seek(pos->filePos, SEEK_SET);
|
|
if (_handle->err()) {
|
|
warning("Unable to rewind SMUSH movie (seek failed)");
|
|
return false;
|
|
}
|
|
memcpy(_inBuf, pos->tmpBuf, BUFFER_SIZE);
|
|
if (inflateCopy(&_stream, &pos->streamBuf) != Z_OK) {
|
|
warning("Unable to rewind SMUSH movie (z-lib copy handle failed)");
|
|
return false;
|
|
}
|
|
_fileDone = false;
|
|
return true;
|
|
}
|
|
|
|
bool zlibFile::open(const char *filename) {
|
|
char flags = 0;
|
|
_inBuf = new byte[BUFFER_SIZE];
|
|
memset(_inBuf, 0, BUFFER_SIZE);
|
|
|
|
if (_handle) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
|
|
warning("zlibFile::open() File %s already opened", filename);
|
|
return false;
|
|
}
|
|
|
|
if (!filename || *filename == 0)
|
|
return false;
|
|
|
|
_handle = g_resourceloader->openNewStreamFile(filename);
|
|
if (!_handle->isOpen()) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
|
|
warning("zlibFile::open() zlibFile %s not found", filename);
|
|
return false;
|
|
}
|
|
|
|
// Read in the GZ header
|
|
_handle->read(_inBuf, 2); // Header
|
|
_handle->read(_inBuf, 1); // Method
|
|
_handle->read(_inBuf, 1); // Flags
|
|
flags = _inBuf[0];
|
|
_handle->read(_inBuf, 6); // XFlags
|
|
|
|
// Xtra & Comment
|
|
if (flags & 0x04 || flags & 0x10) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_ERROR || gDebugLevel == DEBUG_ALL) {
|
|
error("zlibFile::open() Unsupported header flag");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (flags & 0x08) { // Orig. Name
|
|
do {
|
|
_handle->read(_inBuf, 1);
|
|
} while(_inBuf[0]);
|
|
}
|
|
|
|
if (flags & 0x02) // CRC
|
|
_handle->read(_inBuf, 2);
|
|
|
|
memset(_inBuf, 0, BUFFER_SIZE - 1); // Zero buffer (debug)
|
|
_stream.zalloc = NULL;
|
|
_stream.zfree = NULL;
|
|
_stream.opaque = Z_NULL;
|
|
|
|
if (inflateInit2(&_stream, -15) != Z_OK && (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_ERROR || gDebugLevel == DEBUG_ALL))
|
|
error("zlibFile::open() inflateInit2 failed");
|
|
|
|
_stream.next_in = NULL;
|
|
_stream.next_out = NULL;
|
|
_stream.avail_in = 0;
|
|
_stream.avail_out = BUFFER_SIZE - 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
void zlibFile::close() {
|
|
if (_handle) {
|
|
_handle->close();
|
|
delete _handle;
|
|
_handle = NULL;
|
|
}
|
|
|
|
delete[] _inBuf;
|
|
_inBuf = NULL;
|
|
}
|
|
|
|
bool zlibFile::isOpen() {
|
|
return _handle->isOpen();
|
|
}
|
|
|
|
uint32 zlibFile::read(void *ptr, uint32 len) {
|
|
int result = Z_OK;
|
|
bool fileEOF = false;
|
|
|
|
if (!_handle->isOpen()) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_ERROR || gDebugLevel == DEBUG_ALL)
|
|
error("zlibFile::read() File is not open");
|
|
return 0;
|
|
}
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
_stream.next_out = (Bytef *)ptr;
|
|
_stream.avail_out = len;
|
|
|
|
_fileDone = false;
|
|
while (_stream.avail_out) {
|
|
if (_stream.avail_in == 0) { // !EOF
|
|
_stream.avail_in = _handle->read(_inBuf, BUFFER_SIZE - 1);
|
|
if (_stream.avail_in == 0) {
|
|
fileEOF = true;
|
|
break;
|
|
}
|
|
_stream.next_in = (Byte *)_inBuf;
|
|
}
|
|
|
|
result = inflate(&_stream, Z_NO_FLUSH);
|
|
if (result == Z_STREAM_END) { // EOF
|
|
// "Stream end" is zlib's way of saying that it's done after the current call,
|
|
// so as long as no calls are made after we've received this message we're OK
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
|
|
printf("zlibFile::read() Stream ended\n");
|
|
_fileDone = true;
|
|
break;
|
|
}
|
|
if (result == Z_DATA_ERROR) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
|
|
warning("zlibFile::read() Decompression error");
|
|
_fileDone = true;
|
|
break;
|
|
}
|
|
if (result != Z_OK || fileEOF) {
|
|
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
|
|
warning("zlibFile::read() Unknown decomp result: %d/%d", result, fileEOF);
|
|
_fileDone = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (uint32)(len - _stream.avail_out);
|
|
}
|
|
|
|
byte zlibFile::readByte() {
|
|
unsigned char c;
|
|
|
|
read(&c, 1);
|
|
return c;
|
|
}
|
|
|
|
uint16 zlibFile::readUint16LE() {
|
|
uint16 a = readByte();
|
|
uint16 b = readByte();
|
|
return a | (b << 8);
|
|
}
|
|
|
|
uint32 zlibFile::readUint32LE() {
|
|
uint32 a = readUint16LE();
|
|
uint32 b = readUint16LE();
|
|
return (b << 16) | a;
|
|
}
|
|
|
|
uint16 zlibFile::readUint16BE() {
|
|
uint16 b = readByte();
|
|
uint16 a = readByte();
|
|
return a | (b << 8);
|
|
}
|
|
|
|
uint32 zlibFile::readUint32BE() {
|
|
uint32 b = readUint16BE();
|
|
uint32 a = readUint16BE();
|
|
return (b << 16) | a;
|
|
}
|
|
|
|
} // end of namespace Grim
|