/* ScummVM - Graphic Adventure Engine * * ScummVM 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 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ /*! \file * \todo Make resource manager class and make load* functions its members */ #include "common/endian.h" #include "common/stream.h" #include "cine/cine.h" #include "cine/anim.h" #include "cine/gfx.h" #include "cine/pal.h" #include "cine/part.h" #include "cine/various.h" namespace Cine { struct AnimHeader2Struct { uint32 field_0; uint16 width; uint16 height; uint16 type; uint16 field_A; uint16 field_C; uint16 field_E; }; AnimData animDataTable[NUM_MAX_ANIMDATA]; static const AnimDataEntry transparencyData[] = { {"ALPHA", 0xF}, {"TITRE", 0xF}, {"TITRE2", 0xF}, {"ET", 0xC}, {"L311", 0x3}, {"L405", 0x1}, {"L515", 0xC}, {"L009", 0xE}, {"L010", 0xE}, {"FUTUR", 0x6}, {"PAYSAN3", 0xB}, {"L801", 0xC}, {"L802", 0xC}, {"L803", 0xC}, {"L901", 0xD}, {"L902", 0x8}, {"L903", 0xD}, {"L904", 0xD}, {"L905", 0xD}, {"L906", 0xD}, {"L907", 0xD}, {"LA03", 0x4}, {"MOINE", 0xB}, {"L908", 0x8}, {"L909", 0x8}, {"L807", 0xC}, {"L808", 0xC}, {"LA01", 0xB}, {"L1201", 0xC}, {"L1202", 0xC}, {"L1203", 0xC}, {"L1210", 0x5}, {"L1211", 0xC}, {"L1214", 0xC}, {"L1215", 0xC}, {"L1216", 0xC}, {"L1217", 0xC}, {"L1218", 0xC}, {"L1219", 0xC}, {"L1220", 0xC}, {"SEIGNEUR", 0x6}, {"PERE0", 0xD}, {"L1302", 0x4}, {"L1303", 0x4}, {"L1304", 0x4}, {"L1401", 0xF}, {"L1402", 0xF}, {"L1501", 0x8}, {"L1503", 0x8}, {"L1504", 0x4}, {"L1505", 0x8}, {"L1506", 0x8}, {"L1601", 0xB}, {"L1602", 0xB}, {"L1603", 0xB}, {"L1604", 0x4}, {"L1605", 0x4}, {"L1701", 0x4}, {"L1702", 0x4}, {"L1801", 0x6}, {"L1904", 0x8}, {"L2002", 0x8}, {"L2003", 0x8}, {"L2101", 0x4}, {"L2102", 0x4}, {"L2201", 0x7}, {"L2202", 0x7}, {"L2203", 0xE}, {"L2305", 0x9}, {"L2306", 0x9}, {"GARDE1", 0x7}, {"L2402", 0x7}, {"L2407", 0x7}, {"L2408", 0x7}, {"GARDE2", 0x6}, {"L2601", 0x6}, {"L2602", 0x6}, {"L2603", 0x6}, {"L2604", 0x6}, {"L2605", 0x8}, {"L2606", 0x8}, {"L2607", 0x8}, {"L2610", 0x6}, {"L2611", 0x6}, {"L2612", 0x6}, {"L2613", 0x8}, {"L2614", 0x6}, {"VOYAGEUR", 0x6}, {"L2701", 0xD}, {"L2702", 0xD}, {"L2703", 0x6}, {"L2801", 0xD}, {"L2802", 0xD}, {"L2803", 0xD}, {"L2804", 0xD}, {"L2807", 0xD}, {"L2902", 0x8}, {"L2903", 0x8}, {"L3101", 0xA}, {"L3102", 0xA}, {"L3103", 0xA}, {"L3203", 0xF}, {"L3204", 0xF}, {"L3001", 0x7}, {"L3002", 0x7}, {"L3416", 0xC}, {"L3601", 0x5}, {"L3602", 0x5}, {"L3603", 0x5}, {"L3607", 0x5}, {"L3701", 0x8}, {"L3702", 0x8}, {"L3703", 0x8}, {"L4001", 0xD}, {"L4002", 0xD}, {"L4103", 0xF}, {"L4106", 0xF}, {"CRUGHON1", 0xC}, {"L4203", 0xC}, {"L4301", 0xC}, {"L4302", 0xC}, {"L4303", 0xC}, {"FUTUR2", 0x6}, {"L4601", 0xE}, {"L4603", 0x1}, {"L4106", 0xF}, {"L4801", 0xD}, {"L4802", 0xD}, {"FIN01", 0xB}, {"FIN02", 0xB}, {"FIN03", 0xB}, {"FIN", 0x9}, }; void convertMask(byte *dest, const byte *source, int16 width, int16 height); void convert8BBP(byte *dest, const byte *source, int16 width, int16 height); void convert8BBP2(byte *dest, byte *source, int16 width, int16 height); AnimData::AnimData() : _width(0), _height(0), _bpp(0), _var1(0), _data(NULL), _mask(NULL), _fileIdx(-1), _frameIdx(-1), _realWidth(0), _size(0) { memset(_name, 0, sizeof(_name)); } /*! \brief Copy constructor */ AnimData::AnimData(const AnimData &src) : _width(src._width), _height(src._height), _bpp(src._bpp), _var1(src._var1), _data(NULL), _mask(NULL), _fileIdx(src._fileIdx), _frameIdx(src._frameIdx), _realWidth(src._realWidth), _size(src._size) { if (src._data) { _data = new byte[_size]; assert(_data); memcpy(_data, src._data, _size*sizeof(byte)); } if(src._mask) { _mask = new byte[_size]; assert(_mask); memcpy(_mask, src._mask, _size*sizeof(byte)); } memset(_name, 0, sizeof(_name)); strcpy(_name, src._name); } /*! \brief Destructor */ AnimData::~AnimData() { clear(); } /*! \brief Assingment operator */ AnimData &AnimData::operator=(const AnimData &src) { AnimData tmp = src; byte *ptr; _width = tmp._width; _height = tmp._height; _bpp = tmp._bpp; _var1 = tmp._var1; ptr = _data; _data = tmp._data; tmp._data = ptr; ptr = _mask; _mask = tmp._mask; tmp._mask = ptr; _fileIdx = tmp._fileIdx; _frameIdx = tmp._frameIdx; memset(_name, 0, sizeof(_name)); strcpy(_name, tmp._name); _realWidth = tmp._realWidth; _size = tmp._size; return *this; } byte AnimData::getColor(int x, int y) { assert(_data); assert(x >= 0 && x < _realWidth && y >= 0 && y <= _height); assert(x + y * _realWidth < _size); return _data[x + y * _realWidth]; } /*! \brief Load and decode image frame * \param d Encoded image data * \param type Encoding type * \param w Image width * \param h Image height * \param file Data file index in bundle * \param frame Image frame index * \param n Part name * \param transparent Transparent color (for ANIM_MASKSPRITE) */ void AnimData::load(byte *d, int type, uint16 w, uint16 h, int16 file, int16 frame, const char *n, byte transparent) { assert(d); if (_data) { clear(); } _width = w * 2; _height = h; _var1 = _width >> 3; _data = NULL; _mask = NULL; _fileIdx = file; _frameIdx = frame; memset(_name, 0, sizeof(_name)); strcpy(_name, n); _realWidth = w; switch (type) { case ANIM_RAW: _width = w; _var1 = w >> 3; _bpp = 4; _size = w * h; _data = new byte[_size]; assert(_data); memcpy(_data, d, _size*sizeof(byte)); break; case ANIM_MASK: _bpp = 1; _size = w * h * 8; _data = new byte[_size]; _realWidth = w * 8; assert(_data); convertMask(_data, d, w, h); break; case ANIM_SPRITE: _bpp = 4; _size = w * h * 2; _data = new byte[_size]; _realWidth = w * 2; assert(_data); gfxConvertSpriteToRaw(_data, d, w, h); break; case ANIM_MASKSPRITE: _bpp = 4; _size = w * h * 2; _data = new byte[_size]; _mask = new byte[_size]; _realWidth = w * 2; assert(_data && _mask); gfxConvertSpriteToRaw(_data, d, w, h); generateMask(_data, _mask, _size, transparent); break; case ANIM_PALSPRITE: _bpp = 5; _size = w * h * 2; _data = new byte[_size]; _realWidth = w * 2; assert(_data); convert8BBP(_data, d, w, h); break; case ANIM_FULLSPRITE: _bpp = 8; _var1 = _width >> 4; _size = w * h; _data = new byte[_size]; assert(_data); convert8BBP2(_data, d, w, h); break; default: error("AnimData::load: unknown image type"); } } /*! \brief Reset image */ void AnimData::clear() { delete[] _data; delete[] _mask; _width = 0; _height = 0; _bpp = 0; _var1 = 0; _data = NULL; _mask = NULL; _fileIdx = -1; _frameIdx = -1; memset(_name, 0, sizeof(_name)); _size = 0; } /*! \brief Write image identifiers to savefile * \param fHandle Savefile open for writing */ void AnimData::save(Common::OutSaveFile &fHandle) const { fHandle.writeUint16BE(_width); fHandle.writeUint16BE(_var1); fHandle.writeUint16BE(_bpp); fHandle.writeUint16BE(_height); fHandle.writeUint32BE(_data != NULL); // _data fHandle.writeUint32BE(_mask != NULL); // _mask fHandle.writeUint16BE(_fileIdx); fHandle.writeUint16BE(_frameIdx); fHandle.write(_name, sizeof(_name)); } /*! \brief Clear part of animDataTable * \param startIdx First image frame to be cleared * \param numIdx Number of image frames to be cleared */ void freeAnimDataRange(byte startIdx, byte numIdx) { for (byte i = 0; i < numIdx; i++) { animDataTable[startIdx + i].clear(); } } /*! \brief Clear whole animDataTable */ void freeAnimDataTable() { freeAnimDataRange(0, NUM_MAX_ANIMDATA); } /*! \brief Find transparent color index for image * \brief animName Image file name */ static byte getAnimTransparentColor(const char *animName) { char name[15]; removeExtention(name, animName); for (int i = 0; i < ARRAYSIZE(transparencyData); i++) { if (!strcmp(name, transparencyData[i].name)) { return transparencyData[i].color; } } return 0; } /*! \brief Generate mask for image * \param[in] sprite Image data * \param[out] mask Image mask * \param size Image data length * \param transparency Transparent color index */ void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency) { for (uint16 i = 0; i < size; i++) { if (*(sprite++) != transparency) { *(mask++) = 0; } else { *(mask++) = 1; } } } /*! \brief Decode 1bpp mask * \param[out] dest Decoded mask * \param[in] source Encoded mask * \param width Mask width * \param height Mask height */ void convertMask(byte *dest, const byte *source, int16 width, int16 height) { int16 i, j; byte maskEntry; for (i = 0; i < width * height; i++) { maskEntry = *(source++); for (j = 0; j < 8; j++) { *(dest++) = (maskEntry & 0x80) ? 0 : 1; maskEntry <<= 1; } } } /*! \brief Decode 4bpp sprite * \param[out] dest Decoded image * \param[in] source Encoded image * \param width Image width * \param height Image height */ void convert4BBP(byte *dest, const byte *source, int16 width, int16 height) { byte maskEntry; for (int16 i = 0; i < width * height; i++) { maskEntry = *(source++); *(dest++) = (maskEntry & 0xF0) >> 4; *(dest++) = (maskEntry & 0xF); } } /*! \brief Read image header * \param[out] animHeader Image header reference * \param readS Input stream open for reading */ void loadAnimHeader(AnimHeaderStruct &animHeader, Common::MemoryReadStream readS) { animHeader.field_0 = readS.readByte(); animHeader.field_1 = readS.readByte(); animHeader.field_2 = readS.readByte(); animHeader.field_3 = readS.readByte(); animHeader.frameWidth = readS.readUint16BE(); animHeader.frameHeight = readS.readUint16BE(); animHeader.field_8 = readS.readByte(); animHeader.field_9 = readS.readByte(); animHeader.field_A = readS.readByte(); animHeader.field_B = readS.readByte(); animHeader.field_C = readS.readByte(); animHeader.field_D = readS.readByte(); animHeader.numFrames = readS.readUint16BE(); animHeader.field_10 = readS.readByte(); animHeader.field_11 = readS.readByte(); animHeader.field_12 = readS.readByte(); animHeader.field_13 = readS.readByte(); animHeader.field_14 = readS.readUint16BE(); } /*! \brief Find next empty space animDataTable * \param start First index to check */ int emptyAnimSpace(int start = 0) { for (; start < NUM_MAX_ANIMDATA; start++) { if (!animDataTable[start].data()) { return start; } } return -1; } /*! \brief Load SPL data into animDataTable * \param resourceName SPL filename * \param idx Target index in animDataTable */ void loadSpl(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry; if (foundFileIdx < 0) { return; } byte *dataPtr = readBundleFile(foundFileIdx); entry = idx < 0 ? emptyAnimSpace() : idx; assert(entry >= 0); animDataTable[entry].load(dataPtr, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName); free(dataPtr); } /*! \brief Load 1bpp mask * \param resourceName Mask filename */ void loadMsk(const char *resourceName) { int16 foundFileIdx = findFileInBundle(resourceName); int entry = 0; byte *dataPtr = readBundleFile(foundFileIdx); byte *ptr; AnimHeaderStruct animHeader; Common::MemoryReadStream readS(dataPtr, 0x16); loadAnimHeader(animHeader, readS); ptr = dataPtr + 0x16; for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { entry = emptyAnimSpace(entry); assert(entry >= 0); animDataTable[entry].load(ptr, ANIM_MASK, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName); ptr += animHeader.frameWidth * animHeader.frameHeight; } free(dataPtr); } /*! \brief Load animation * \param resourceName Animation filename */ void loadAni(const char *resourceName) { int16 foundFileIdx = findFileInBundle(resourceName); int entry = 0; byte *dataPtr = readBundleFile(foundFileIdx); byte *ptr; byte transparentColor; AnimHeaderStruct animHeader; Common::MemoryReadStream readS(dataPtr, 0x16); loadAnimHeader(animHeader, readS); ptr = dataPtr + 0x16; transparentColor = getAnimTransparentColor(resourceName); for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { entry = emptyAnimSpace(entry); assert(entry >= 0); // special case transparency handling if (!strcmp(resourceName, "L2202.ANI")) { transparentColor = i < 2 ? 0 : 7; } else if (!strcmp(resourceName, "L4601.ANI")) { transparentColor = i < 1 ? 0xE : 0; } animDataTable[entry].load(ptr, ANIM_MASKSPRITE, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName, transparentColor); ptr += animHeader.frameWidth * animHeader.frameHeight; } free(dataPtr); } /*! \brief Decode 16 color image with palette * \param[out] dest Decoded image * \param[in] source Encoded image * \param width Image width * \param height Image height */ void convert8BBP(byte *dest, const byte *source, int16 width, int16 height) { const byte *table = source; byte color; source += 16; for (uint16 i = 0; i < width * height; i++) { color = *(source++); *(dest++) = table[color >> 4]; *(dest++) = table[color & 0xF]; } } /*! \brief Decode 8bit image * \param[out] dest Decoded image * \param[in] source Encoded image * \param width Image width * \param height Image height * \attention Data in source are destroyed during decoding */ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) { uint16 i, j; int k, m; byte color; for (j = 0; j < (width * height) / 16; j++) { // m = 0: even bits, m = 1: odd bits for (m = 0; m <= 1; m++) { for (i = 0; i < 8; i++) { color = 0; for (k = 14 + m; k >= 0; k -= 2) { color |= ((*(source + k) & 0x080) >> 7); *(source + k) <<= 1; if (k > 0 + m) color <<= 1; } // end k *(dest++) = color; } // end i } // end m source += 0x10; } // end j } /*! \brief Load image set * \param resourceName Image set filename * \param idx Target index in animDataTable */ void loadSet(const char *resourceName, int16 idx) { AnimHeader2Struct header2; uint16 numSpriteInAnim; int16 foundFileIdx = findFileInBundle(resourceName); int16 entry = idx >= 0 ? idx : 0; byte *ptr, *startOfDataPtr, *dataPtr, *origDataPtr; int type; origDataPtr = dataPtr = readBundleFile(foundFileIdx); assert(!memcmp(dataPtr, "SET", 3)); ptr = dataPtr + 4; numSpriteInAnim = READ_BE_UINT16(ptr); ptr += 2; startOfDataPtr = ptr + numSpriteInAnim * 0x10; for (int16 i = 0; i < numSpriteInAnim; i++, entry++) { Common::MemoryReadStream readS(ptr, 0x10); header2.field_0 = readS.readUint32BE(); header2.width = readS.readUint16BE(); header2.height = readS.readUint16BE(); header2.type = readS.readUint16BE(); header2.field_A = readS.readUint16BE(); header2.field_C = readS.readUint16BE(); header2.field_E = readS.readUint16BE(); ptr += 0x10; entry = idx < 0 ? emptyAnimSpace(entry) : idx + i; assert(entry >= 0); dataPtr = startOfDataPtr + header2.field_0; if (header2.type == 1) { type = ANIM_MASK; } else if (header2.type == 4) { type = ANIM_SPRITE; } else if (header2.type == 5) { type = ANIM_PALSPRITE; } else { type = ANIM_FULLSPRITE; } animDataTable[entry].load(dataPtr, type, header2.width, header2.height, foundFileIdx, i, currentPartName); } free(origDataPtr); } /*! \brief Load SEQ data into animDataTable * \param resourceName SEQ data filename * \param idx Target index in animDataTable */ void loadSeq(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); byte *dataPtr = readBundleFile(foundFileIdx); int entry = idx < 0 ? emptyAnimSpace() : idx; animDataTable[entry].load(dataPtr+0x16, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize-0x16, 1, foundFileIdx, 0, currentPartName); free(dataPtr); } void loadResource(const char *resourceName) { /* byte isMask = 0; */ /* byte isSpl = 0; */ if (strstr(resourceName, ".SPL")) { loadSpl(resourceName, -1); return; } else if (strstr(resourceName, ".MSK")) { loadMsk(resourceName); return; } else if (strstr(resourceName, ".ANI")) { loadAni(resourceName); return; } else if (strstr(resourceName, ".ANM")) { loadAni(resourceName); return; } else if (strstr(resourceName, ".SET")) { loadSet(resourceName, -1); return; } else if (strstr(resourceName, ".SEQ")) { loadSeq(resourceName, -1); return; } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure exitEngine = 1; return; } error("loadResource: Cannot determine type for '%s'", resourceName); } /*! \todo There seems to be some additional resource file that is not loaded */ void loadAbs(const char *resourceName, uint16 idx) { /* byte isMask = 0; */ /* byte isSpl = 0; */ if (strstr(resourceName, ".SET")) { loadSet(resourceName, idx); return; } else if (strstr(resourceName, ".H32")) { warning("Ignoring file %s (load at %d)", resourceName, idx); return; } else if (strstr(resourceName, ".SEQ")) { loadSeq(resourceName, idx); return; } else if (strstr(resourceName, ".SPL")) { loadSpl(resourceName, idx); return; } else if (strstr(resourceName, ".AMI")) { warning("Ignoring file %s (load at %d)", resourceName, idx); return; } else if (strstr(resourceName, ".ANI")) { warning("Ignoring file %s (load at %d)", resourceName, idx); return; } error("loadAbs: Cannot determine type for '%s'", resourceName); } /*! \brief Load animDataTable from save * \param fHandle Savefile open for reading * \param saveGameFormat The used savegame format * \todo Add Operation Stealth savefile support * * Unlike the old code, this one actually rebuilds the table one frame * at a time. */ void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) { int16 currentAnim, foundFileIdx; int8 isMask = 0, isSpl = 0; byte *dataPtr, *ptr; char *animName, part[256]; byte transparentColor = 0; AnimHeaderStruct animHeader; uint16 width, height, bpp, var1; int16 frame; char name[10]; int type; strcpy(part, currentPartName); // We only support these variations of the savegame format at the moment. assert(saveGameFormat == ANIMSIZE_23 || saveGameFormat == ANIMSIZE_30_PTRS_INTACT); const int entrySize = ((saveGameFormat == ANIMSIZE_23) ? 23 : 30); const int fileStartPos = fHandle.pos(); for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim += animHeader.numFrames) { // Initialize the number of frames variable to a sane number. // This is needed when using continue later in this function. animHeader.numFrames = 1; // Seek to the start of the current animation's entry fHandle.seek(fileStartPos + currentAnim * entrySize); // Read in the current animation entry width = fHandle.readUint16BE(); var1 = fHandle.readUint16BE(); bpp = fHandle.readUint16BE(); height = fHandle.readUint16BE(); bool validPtr = false; // Handle variables only present in animation entries of size 30 if (entrySize == 30) { validPtr = (fHandle.readUint32BE() != 0); // Read data pointer fHandle.readUint32BE(); // Discard mask pointer } foundFileIdx = fHandle.readSint16BE(); frame = fHandle.readSint16BE(); fHandle.read(name, 10); // Handle variables only present in animation entries of size 23 if (entrySize == 23) { validPtr = (fHandle.readByte() != 0); } // Don't try to load invalid entries. if (foundFileIdx < 0 || !validPtr) { continue; } // Alright, the animation entry looks to be valid so let's start handling it... if (strcmp(currentPartName, name)) { closePart(); loadPart(name); } animName = partBuffer[foundFileIdx].partName; ptr = dataPtr = readBundleFile(foundFileIdx); // isSpl and isMask are mutually exclusive cases isSpl = (strstr(animName, ".SPL")) ? 1 : 0; isMask = (strstr(animName, ".MSK")) ? 1 : 0; if (isSpl) { width = (uint16) partBuffer[foundFileIdx].unpackedSize; height = 1; animHeader.numFrames = 1; type = ANIM_RAW; } else { Common::MemoryReadStream readS(ptr, 0x16); loadAnimHeader(animHeader, readS); ptr += 0x16; width = animHeader.frameWidth; height = animHeader.frameHeight; if (isMask) { type = ANIM_MASK; } else { type = ANIM_MASKSPRITE; } } loadRelatedPalette(animName); transparentColor = getAnimTransparentColor(animName); // Make sure we load at least one frame and also that we // don't overflow the animDataTable by writing beyond its end. animHeader.numFrames = CLIP(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim); // Load the frames for (frame = 0; frame < animHeader.numFrames; frame++) { // special case transparency handling if (!strcmp(animName, "L2202.ANI")) { transparentColor = (frame < 2) ? 0 : 7; } else if (!strcmp(animName, "L4601.ANI")) { transparentColor = (frame < 1) ? 0xE : 0; } // Load a single frame animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor); } free(dataPtr); } loadPart(part); // Make sure we jump over all the animation entries fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize); } } // End of namespace Cine