/* ScummVM - Scumm Interpreter * Copyright (C) 2006 The ScummVM 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 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$ * */ #include "common/stdafx.h" #include "sound/flac.h" #include "sound/mixer.h" #include "sound/mp3.h" #include "sound/voc.h" #include "sound/vorbis.h" #include "touche/midi.h" #include "touche/touche.h" #include "touche/graphics.h" namespace Touche { enum { kCurrentSpeechDataVersion = 1, kSpeechDataFileHeaderSize = 4 }; struct CompressedSpeechFile { const char *filename; Audio::AudioStream *(*makeStream)(Common::File *file, uint32 size); }; static const CompressedSpeechFile compressedSpeechFilesTable[] = { #ifdef USE_FLAC { "TOUCHE.SOF", Audio::makeFlacStream }, #endif #ifdef USE_VORBIS { "TOUCHE.SOG", Audio::makeVorbisStream }, #endif #ifdef USE_MAD { "TOUCHE.SO3", Audio::makeMP3Stream }, #endif { 0, 0 } }; void ToucheEngine::res_openDataFile() { if (!_fData.open("TOUCHE.DAT")) { error("Unable to open 'TOUCHE.DAT' for reading"); } for (int i = 0; compressedSpeechFilesTable[i].filename; ++i) { if (_fSpeech[0].open(compressedSpeechFilesTable[i].filename)) { _compressedSpeechData = i; return; } } // _fSpeech[0] opening/closing is driven by the scripts _fSpeech[1].open("OBJ"); _compressedSpeechData = -1; } void ToucheEngine::res_closeDataFile() { _fData.close(); _fSpeech[0].close(); _fSpeech[1].close(); } void ToucheEngine::res_allocateTables() { _fData.seek(64); uint32 textDataOffs = _fData.readUint32LE(); uint32 textDataSize = _fData.readUint32LE(); _textData = (uint8 *)malloc(textDataSize); if (!_textData) { error("Unable to allocate memory for text data"); } _fData.seek(textDataOffs); _fData.read(_textData, textDataSize); _fData.seek(2); const int bw = _fData.readUint16LE(); const int bh = _fData.readUint16LE(); uint32 size = bw * bh; _backdropBuffer = (uint8 *)malloc(size); if (!_backdropBuffer) { error("Unable to allocate memory for backdrop buffer"); } _menuKitData = (uint8 *)malloc(42 * 120); if (!_menuKitData) { error("Unable to allocate memory for menu kit data"); } _convKitData = (uint8 *)malloc(152 * 80); if (!_convKitData) { error("Unable to allocate memory for conv kit data"); } for (int i = 0; i < NUM_SEQUENCES; ++i) { _sequenceDataTable[i] = (uint8 *)malloc(16384); if (!_sequenceDataTable[i]) { error("Unable to allocate memory for sequence data %d", i); } } _programData = (uint8 *)malloc(kMaxProgramDataSize); if (!_programData) { error("Unable to allocate memory for program data"); } _mouseData = (uint8 *)malloc(kCursorWidth * kCursorHeight); if (!_mouseData) { error("Unable to allocate memory for mouse data"); } _iconData = (uint8 *)malloc(kIconWidth * kIconHeight); if (!_iconData) { error("Unable to allocate memory for object data"); } memset(_spritesTable, 0, sizeof(_spritesTable)); _offscreenBuffer = (uint8 *)malloc(kScreenWidth * kScreenHeight); if (!_offscreenBuffer) { error("Unable to allocate memory for offscreen buffer"); } } void ToucheEngine::res_deallocateTables() { free(_textData); _textData = 0; free(_backdropBuffer); _backdropBuffer = 0; free(_menuKitData); _menuKitData = 0; free(_convKitData); _convKitData = 0; for (int i = 0; i < NUM_SEQUENCES; ++i) { free(_sequenceDataTable[i]); _sequenceDataTable[i] = 0; } free(_programData); _programData = 0; free(_mouseData); _mouseData = 0; free(_iconData); _iconData = 0; for (int i = 0; i < NUM_SPRITES; ++i) { free(_spritesTable[i].ptr); _spritesTable[i].ptr = 0; } free(_offscreenBuffer); _offscreenBuffer = 0; } uint32 ToucheEngine::res_getDataOffset(ResourceType type, int num, uint32 *size) { debugC(9, kDebugResource, "ToucheEngine::res_getDataOffset() type=%d num=%d", type, num); static const struct ResourceData { int offs; int count; int type; } dataTypesTable[] = { { 0x048, 100, kResourceTypeRoomImage }, { 0x228, 30, kResourceTypeSequence }, { 0x2A0, 50, kResourceTypeSpriteImage }, { 0x390, 100, kResourceTypeIconImage }, { 0x6B0, 80, kResourceTypeRoomInfo }, { 0x908, 150, kResourceTypeProgram }, { 0xB60, 50, kResourceTypeMusic }, { 0xC28, 120, kResourceTypeSound } }; const ResourceData *rd = NULL; for (unsigned int i = 0; i < ARRAYSIZE(dataTypesTable); ++i) { if (dataTypesTable[i].type == type) { rd = &dataTypesTable[i]; break; } } if (rd == NULL) { error("Invalid resource type %d", type); } if (num < 0 || num > rd->count) { error("Invalid resource number %d (type %d)", num, type); } _fData.seek(rd->offs + num * 4); uint32 offs = _fData.readUint32LE(); assert(offs != 0); if (size) { uint32 nextOffs = _fData.readUint32LE(); *size = nextOffs - offs; } return offs; } void ToucheEngine::res_loadSpriteImage(int num, uint8 *dst) { debugC(9, kDebugResource, "ToucheEngine::res_loadSpriteImage() num=%d", num); const uint32 offs = res_getDataOffset(kResourceTypeSpriteImage, num); _fData.seek(offs); _currentImageWidth = _fData.readUint16LE(); _currentImageHeight = _fData.readUint16LE(); for (int i = 0; i < _currentImageHeight; ++i) { res_decodeScanLineImageRLE(dst + _currentImageWidth * i, _currentImageWidth); } } void ToucheEngine::res_loadProgram(int num) { debugC(9, kDebugResource, "ToucheEngine::res_loadProgram() num=%d", num); const uint32 offs = res_getDataOffset(kResourceTypeProgram, num, &_programDataSize); _fData.seek(offs); assert(_programDataSize <= kMaxProgramDataSize); _fData.read(_programData, _programDataSize); } void ToucheEngine::res_decodeProgramData() { debugC(9, kDebugResource, "ToucheEngine::res_decodeProgramData()"); uint8 *p; uint8 *programDataEnd = _programData + _programDataSize; p = _programData + READ_LE_UINT32(_programData + 32); _script.init(p); p = _programData + READ_LE_UINT32(_programData + 4); _programTextDataPtr = p; _programRectsTable.clear(); p = _programData + READ_LE_UINT32(_programData + 20); while (p < programDataEnd) { int16 x = READ_LE_UINT16(p); p += 2; int16 y = READ_LE_UINT16(p); p += 2; int16 w = READ_LE_UINT16(p); p += 2; int16 h = READ_LE_UINT16(p); p += 2; _programRectsTable.push_back(Common::Rect(x, y, x + w, y + h)); if (x == -1) { break; } } _programPointsTable.clear(); p = _programData + READ_LE_UINT32(_programData + 24); while (p < programDataEnd) { ProgramPointData ppd; ppd.x = READ_LE_UINT16(p); p += 2; ppd.y = READ_LE_UINT16(p); p += 2; ppd.z = READ_LE_UINT16(p); p += 2; ppd.priority = READ_LE_UINT16(p); p += 2; _programPointsTable.push_back(ppd); if (ppd.x == -1) { break; } } _programWalkTable.clear(); p = _programData + READ_LE_UINT32(_programData + 28); while (p < programDataEnd) { ProgramWalkData pwd; pwd.point1 = READ_LE_UINT16(p); p += 2; if (pwd.point1 == -1) { break; } assert((uint16)pwd.point1 < _programPointsTable.size()); pwd.point2 = READ_LE_UINT16(p); p += 2; assert((uint16)pwd.point2 < _programPointsTable.size()); pwd.clippingRect = READ_LE_UINT16(p); p += 2; pwd.area1 = READ_LE_UINT16(p); p += 2; pwd.area2 = READ_LE_UINT16(p); p += 2; p += 12; // unused _programWalkTable.push_back(pwd); } _programAreaTable.clear(); p = _programData + READ_LE_UINT32(_programData + 8); while (p < programDataEnd) { ProgramAreaData pad; int16 x = READ_LE_UINT16(p); p += 2; if (x == -1) { break; } int16 y = READ_LE_UINT16(p); p += 2; int16 w = READ_LE_UINT16(p); p += 2; int16 h = READ_LE_UINT16(p); p += 2; pad.area.r = Common::Rect(x, y, x + w, y + h); pad.area.srcX = READ_LE_UINT16(p); p += 2; pad.area.srcY = READ_LE_UINT16(p); p += 2; pad.id = READ_LE_UINT16(p); p += 2; pad.state = READ_LE_UINT16(p); p += 2; pad.animCount = READ_LE_UINT16(p); p += 2; pad.animNext = READ_LE_UINT16(p); p += 2; _programAreaTable.push_back(pad); } _programBackgroundTable.clear(); p = _programData + READ_LE_UINT32(_programData + 12); while (p < programDataEnd) { ProgramBackgroundData pbd; int16 x = READ_LE_UINT16(p); p += 2; if (x == -1) { break; } int16 y = READ_LE_UINT16(p); p += 2; int16 w = READ_LE_UINT16(p); p += 2; int16 h = READ_LE_UINT16(p); p += 2; pbd.area.r = Common::Rect(x, y, x + w, y + h); pbd.area.srcX = READ_LE_UINT16(p); p += 2; pbd.area.srcY = READ_LE_UINT16(p); p += 2; pbd.type = READ_LE_UINT16(p); p += 2; pbd.offset = READ_LE_UINT16(p); p += 2; pbd.scaleMul = READ_LE_UINT16(p); p += 2; pbd.scaleDiv = READ_LE_UINT16(p); p += 2; _programBackgroundTable.push_back(pbd); } _programHitBoxTable.clear(); p = _programData + READ_LE_UINT32(_programData + 16); while (p < programDataEnd) { ProgramHitBoxData phbd; phbd.item = READ_LE_UINT16(p); p += 2; if (phbd.item == 0) { break; } phbd.talk = READ_LE_UINT16(p); p += 2; phbd.state = READ_LE_UINT16(p); p += 2; phbd.str = READ_LE_UINT16(p); p += 2; phbd.defaultStr = READ_LE_UINT16(p); p += 2; for (int i = 0; i < 8; ++i) { phbd.actions[i] = READ_LE_UINT16(p); p += 2; } for (int i = 0; i < 2; ++i) { int16 x = READ_LE_UINT16(p); p += 2; int16 y = READ_LE_UINT16(p); p += 2; int16 w = READ_LE_UINT16(p); p += 2; int16 h = READ_LE_UINT16(p); p += 2; phbd.hitBoxes[i].left = x; phbd.hitBoxes[i].top = y; phbd.hitBoxes[i].right = x + w; phbd.hitBoxes[i].bottom = y + h; } p += 8; // unused _programHitBoxTable.push_back(phbd); } _programActionScriptOffsetTable.clear(); p = _programData + READ_LE_UINT32(_programData + 36); while (p < programDataEnd) { ProgramActionScriptOffsetData pasod; pasod.object1 = READ_LE_UINT16(p); p += 2; if (pasod.object1 == 0) { break; } pasod.action = READ_LE_UINT16(p); p += 2; pasod.object2 = READ_LE_UINT16(p); p += 2; pasod.offset = READ_LE_UINT16(p); p += 2; _programActionScriptOffsetTable.push_back(pasod); } _programConversationTable.clear(); int count = (READ_LE_UINT32(_programData + 44) - READ_LE_UINT32(_programData + 40)) / 6; assert(count >= 0); p = _programData + READ_LE_UINT32(_programData + 40); while (p < programDataEnd && count != 0) { ProgramConversationData pcd; pcd.num = READ_LE_UINT16(p); p += 2; pcd.offset = READ_LE_UINT16(p); p += 2; pcd.msg = READ_LE_UINT16(p); p += 2; _programConversationTable.push_back(pcd); --count; } _programKeyCharScriptOffsetTable.clear(); p = _programData + READ_LE_UINT32(_programData + 44); while (p < programDataEnd) { ProgramKeyCharScriptOffsetData pksod; pksod.keyChar = READ_LE_UINT16(p); p += 2; if (pksod.keyChar == 0) { break; } pksod.offset = READ_LE_UINT16(p); p += 2; _programKeyCharScriptOffsetTable.push_back(pksod); } } void ToucheEngine::res_loadRoom(int num) { debugC(9, kDebugResource, "ToucheEngine::res_loadRoom() num=%d flag115=%d", num, _flagsTable[115]); debug(0, "Setting up room %d", num); const uint32 offsInfo = res_getDataOffset(kResourceTypeRoomInfo, num); _fData.seek(offsInfo); _fData.skip(2); const int roomImageNum = _fData.readUint16LE(); _fData.skip(2); for (int i = 0; i < 256; ++i) { _fData.read(&_paletteBuffer[i * 4], 3); _paletteBuffer[i * 4 + 3] = 0; } const uint32 offsImage = res_getDataOffset(kResourceTypeRoomImage, roomImageNum); _fData.seek(offsImage); res_loadBackdrop(); bool updateScreenPalette = _flagsTable[115] == 0; // Workaround to what appears to be a scripting bug. Scripts 27 and 100 triggers // a palette fading just after loading a room. Catch this, so that only *one* // palette refresh occurs. if ((_currentEpisodeNum == 27 && _currentRoomNum == 56 && num == 34) || (_currentEpisodeNum == 100 && _currentRoomNum == 2 && num == 1)) { updateScreenPalette = false; } if (updateScreenPalette) { updatePalette(); } else { setPalette(0, 255, 0, 0, 0); } _currentRoomNum = num; _updatedRoomAreasTable[0] = 1; _fullRedrawCounter = 1; _roomNeedRedraw = true; _sequenceEntryTable[5].sprNum = -1; _sequenceEntryTable[5].seqNum = -1; _sequenceEntryTable[6].sprNum = -1; _sequenceEntryTable[6].seqNum = -1; } void ToucheEngine::res_loadSprite(int num, int index) { debugC(9, kDebugResource, "ToucheEngine::res_loadSprite() num=%d index=%d", num, index); assert(index >= 0 && index < NUM_SEQUENCES); _sequenceEntryTable[index].sprNum = num; SpriteData *spr = &_spritesTable[index]; const uint32 offs = res_getDataOffset(kResourceTypeSpriteImage, num); _fData.seek(offs); _currentImageWidth = _fData.readUint16LE(); _currentImageHeight = _fData.readUint16LE(); const uint32 size = _currentImageWidth * _currentImageHeight; if (size > spr->size) { debug(8, "Reallocating memory for sprite %d (index %d), %d bytes needed", num, index, size - spr->size); spr->size = size; if (spr->ptr) { spr->ptr = (uint8 *)realloc(spr->ptr, size); } else { spr->ptr = (uint8 *)malloc(size); } if (!spr->ptr) { error("Unable to reallocate memory for sprite %d (%d bytes)", num, size); } } for (int i = 0; i < _currentImageHeight; ++i) { res_decodeScanLineImageRLE(spr->ptr + _currentImageWidth * i, _currentImageWidth); } spr->bitmapWidth = _currentImageWidth; spr->bitmapHeight = _currentImageHeight; if (_flagsTable[268] == 0) { res_loadImageHelper(spr->ptr, _currentImageWidth, _currentImageHeight); } spr->w = _currentImageWidth; spr->h = _currentImageHeight; } void ToucheEngine::res_loadSequence(int num, int index) { debugC(9, kDebugResource, "ToucheEngine::res_loadSequence() num=%d index=%d", num, index); assert(index < NUM_SEQUENCES); _sequenceEntryTable[index].seqNum = num; const uint32 offs = res_getDataOffset(kResourceTypeSequence, num); _fData.seek(offs); _fData.read(_sequenceDataTable[index], 16000); } void ToucheEngine::res_decodeScanLineImageRLE(uint8 *dst, int lineWidth) { int w = 0; while (w < lineWidth) { uint8 code = _fData.readByte(); if ((code & 0xC0) == 0xC0) { int len = code & 0x3F; uint8 color = _fData.readByte(); memset(dst, color, len); dst += len; w += len; } else { *dst = code; ++dst; ++w; } } } void ToucheEngine::res_loadBackdrop() { debugC(9, kDebugResource, "ToucheEngine::res_loadBackdrop()"); _currentBitmapWidth = _fData.readUint16LE(); _currentBitmapHeight = _fData.readUint16LE(); uint8 *dst = _backdropBuffer; for (int i = 0; i < _currentBitmapHeight; ++i) { res_decodeScanLineImageRLE(dst + _currentBitmapWidth * i, _currentBitmapWidth); } _roomWidth = _currentBitmapWidth; dst = _backdropBuffer; for (int i = 0; i < _currentBitmapWidth; ++i) { if (*dst == 255) { _roomWidth = i; *dst = 0; break; } ++dst; } } void ToucheEngine::res_loadImage(int num, uint8 *dst) { debugC(9, kDebugResource, "ToucheEngine::res_loadImage() num=%d", num); const uint32 offsInfo = res_getDataOffset(kResourceTypeIconImage, num); _fData.seek(offsInfo); _currentImageWidth = _fData.readUint16LE(); _currentImageHeight = _fData.readUint16LE(); for (int i = 0; i < _currentImageHeight; ++i) { res_decodeScanLineImageRLE(dst + _currentImageWidth * i, _currentImageWidth); } res_loadImageHelper(dst, _currentImageWidth, _currentImageHeight); } void ToucheEngine::res_loadImageHelper(uint8 *imgData, int imgWidth, int imgHeight) { uint8 *p = imgData; for (_currentImageHeight = 0; _currentImageHeight < imgHeight; ++_currentImageHeight, p += imgWidth) { if (*p == 64 || *p == 255) { break; } } p = imgData; for (_currentImageWidth = 0; _currentImageWidth < imgWidth; ++_currentImageWidth, ++p) { if (*p == 64 || *p == 255) { break; } } if (_flagsTable[267] == 0) { for (int i = 0; i < imgWidth * imgHeight; ++i) { uint8 color = imgData[i]; if (color != 0) { if (color < 64) { color += 192; } else { color = 0; } } imgData[i] = color; } } } void ToucheEngine::res_loadSound(int priority, int num) { debugC(9, kDebugResource, "ToucheEngine::res_loadSound() num=%d", num); if (priority >= 0) { uint32 size; const uint32 offs = res_getDataOffset(kResourceTypeSound, num, &size); _fData.seek(offs); Audio::AudioStream *stream = Audio::makeVOCStream(_fData); if (stream) { _mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, stream); } } } void ToucheEngine::res_stopSound() { _mixer->stopHandle(_sfxHandle); } void ToucheEngine::res_loadMusic(int num) { debugC(9, kDebugResource, "ToucheEngine::res_loadMusic() num=%d", num); uint32 size; const uint32 offs = res_getDataOffset(kResourceTypeMusic, num, &size); _fData.seek(offs); _midiPlayer->play(_fData, size, true); } void ToucheEngine::res_loadSpeech(int num) { debugC(9, kDebugResource, "ToucheEngine::res_loadSpeech() num=%d", num); if (num == -1) { res_stopSpeech(); } else { if (_compressedSpeechData < 0) { // uncompressed speech data if (_fSpeech[0].isOpen()) { _fSpeech[0].close(); } char filename[10]; sprintf(filename, "V%d", num); _fSpeech[0].open(filename); } if (_fSpeech[0].isOpen()) { _flagsTable[617] = num; } } } void ToucheEngine::res_loadSpeechSegment(int num) { debugC(9, kDebugResource, "ToucheEngine::res_loadSpeechSegment() num=%d", num); if (_talkTextMode != kTalkModeTextOnly && _flagsTable[617] != 0) { Audio::AudioStream *stream = 0; if (_compressedSpeechData < 0) { // uncompressed speech data int i = 0; if (num >= 750) { num -= 750; i = 1; } if (!_fSpeech[i].isOpen()) { return; } _fSpeech[i].seek(num * 8); uint32 offs = _fSpeech[i].readUint32LE(); uint32 size = _fSpeech[i].readUint32LE(); if (size == 0) { return; } _fSpeech[i].seek(offs); stream = Audio::makeVOCStream(_fSpeech[i]); } else { if (num >= 750) { num -= 750; _fSpeech[0].seek(kSpeechDataFileHeaderSize); } else { assert(_flagsTable[617] > 0 && _flagsTable[617] < 140); _fSpeech[0].seek(kSpeechDataFileHeaderSize + _flagsTable[617] * 4); } uint32 dataOffs = _fSpeech[0].readUint32LE(); if (dataOffs == 0) { return; } _fSpeech[0].seek(dataOffs + num * 8); uint32 offs = _fSpeech[0].readUint32LE(); uint32 size = _fSpeech[0].readUint32LE(); if (size == 0) { return; } _fSpeech[0].seek(offs); stream = (compressedSpeechFilesTable[_compressedSpeechData].makeStream)(&_fSpeech[0], size); } if (stream) { _speechPlaying = true; _mixer->playInputStream(Audio::Mixer::kSpeechSoundType, &_speechHandle, stream); } } } void ToucheEngine::res_stopSpeech() { debugC(9, kDebugResource, "ToucheEngine::res_stopSpeech()"); _mixer->stopHandle(_speechHandle); _speechPlaying = false; } } // namespace Touche