mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 22:28:10 +00:00
1142 lines
32 KiB
C++
1142 lines
32 KiB
C++
/* 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 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/>.
|
|
*
|
|
*/
|
|
|
|
// Resource file routines for Simon1/Simon2
|
|
|
|
|
|
#include "common/archive.h"
|
|
#include "common/file.h"
|
|
#include "common/memstream.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/util.h"
|
|
|
|
#include "agos/agos.h"
|
|
#include "agos/intern.h"
|
|
|
|
#include "common/zlib.h"
|
|
|
|
namespace AGOS {
|
|
|
|
#ifdef ENABLE_AGOS2
|
|
uint16 AGOSEngine_Feeble::to16Wrapper(uint value) {
|
|
return TO_LE_16(value);
|
|
}
|
|
|
|
uint16 AGOSEngine_Feeble::readUint16Wrapper(const void *src) {
|
|
return READ_LE_UINT16(src);
|
|
}
|
|
|
|
uint32 AGOSEngine_Feeble::readUint32Wrapper(const void *src) {
|
|
return READ_LE_UINT32(src);
|
|
}
|
|
#endif
|
|
|
|
uint16 AGOSEngine::to16Wrapper(uint value) {
|
|
return TO_BE_16(value);
|
|
}
|
|
|
|
uint16 AGOSEngine::readUint16Wrapper(const void *src) {
|
|
return READ_BE_UINT16(src);
|
|
}
|
|
|
|
uint32 AGOSEngine::readUint32Wrapper(const void *src) {
|
|
return READ_BE_UINT32(src);
|
|
}
|
|
|
|
void AGOSEngine::decompressData(const char *srcName, byte *dst, uint32 offset, uint32 srcSize, uint32 dstSize) {
|
|
#ifdef USE_ZLIB
|
|
Common::File in;
|
|
in.open(srcName);
|
|
if (in.isOpen() == false)
|
|
error("decompressData: Can't load %s", srcName);
|
|
|
|
in.seek(offset, SEEK_SET);
|
|
if (srcSize != dstSize) {
|
|
byte *srcBuffer = (byte *)malloc(srcSize);
|
|
|
|
if (in.read(srcBuffer, srcSize) != srcSize)
|
|
error("decompressData: Read failed");
|
|
|
|
unsigned long decompressedSize = dstSize;
|
|
if (!Common::uncompress(dst, &decompressedSize, srcBuffer, srcSize))
|
|
error("decompressData: Zlib uncompress error");
|
|
free(srcBuffer);
|
|
} else {
|
|
if (in.read(dst, dstSize) != dstSize)
|
|
error("decompressData: Read failed");
|
|
}
|
|
in.close();
|
|
#else
|
|
error("Zlib support is required for Amiga and Macintosh versions");
|
|
#endif
|
|
}
|
|
|
|
void AGOSEngine::loadOffsets(const char *filename, int number, uint32 &file, uint32 &offset, uint32 &srcSize, uint32 &dstSize) {
|
|
Common::File in;
|
|
|
|
int offsSize = (getPlatform() == Common::kPlatformAmiga) ? 16 : 12;
|
|
|
|
/* read offsets from index */
|
|
in.open(filename);
|
|
if (in.isOpen() == false) {
|
|
error("loadOffsets: Can't load index file '%s'", filename);
|
|
}
|
|
|
|
in.seek(number * offsSize, SEEK_SET);
|
|
offset = in.readUint32LE();
|
|
dstSize = in.readUint32LE();
|
|
srcSize = in.readUint32LE();
|
|
file = in.readUint32LE();
|
|
in.close();
|
|
}
|
|
|
|
int AGOSEngine::allocGamePcVars(Common::SeekableReadStream *in) {
|
|
uint32 itemArraySize, itemArrayInited, stringTableNum;
|
|
uint32 version;
|
|
uint32 i;
|
|
|
|
itemArraySize = in->readUint32BE();
|
|
version = in->readUint32BE();
|
|
itemArrayInited = in->readUint32BE();
|
|
stringTableNum = in->readUint32BE();
|
|
|
|
// First two items are predefined
|
|
if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2) {
|
|
itemArraySize += 2;
|
|
itemArrayInited = itemArraySize;
|
|
} else {
|
|
itemArrayInited += 2;
|
|
itemArraySize += 2;
|
|
}
|
|
|
|
if (version != 0x80)
|
|
error("allocGamePcVars: Not a runtime database");
|
|
|
|
_itemArrayPtr = (Item **)calloc(itemArraySize, sizeof(Item *));
|
|
if (_itemArrayPtr == nullptr)
|
|
error("allocGamePcVars: Out of memory for Item array");
|
|
|
|
_itemArraySize = itemArraySize;
|
|
_itemArrayInited = itemArrayInited;
|
|
|
|
for (i = 1; i < itemArrayInited; i++) {
|
|
_itemArrayPtr[i] = (Item *)allocateItem(sizeof(Item));
|
|
}
|
|
|
|
// The rest is cleared automatically by calloc
|
|
allocateStringTable(stringTableNum + 10);
|
|
_stringTabNum = stringTableNum;
|
|
|
|
return itemArrayInited;
|
|
}
|
|
|
|
void AGOSEngine_PN::loadGamePcFile() {
|
|
if (getFileName(GAME_BASEFILE) != nullptr) {
|
|
Common::File in;
|
|
// Read dataBase
|
|
if (!in.open(getFileName(GAME_BASEFILE))) {
|
|
error("loadGamePcFile: Can't load database file '%s'", getFileName(GAME_BASEFILE));
|
|
}
|
|
|
|
_dataBaseSize = in.size();
|
|
_dataBase = (byte *)malloc(_dataBaseSize);
|
|
if (_dataBase == nullptr)
|
|
error("loadGamePcFile: Out of memory for dataBase");
|
|
in.read(_dataBase, _dataBaseSize);
|
|
|
|
if (_dataBase[31] != 0)
|
|
error("Later version of system requested");
|
|
}
|
|
|
|
if (getFileName(GAME_TEXTFILE) != nullptr) {
|
|
Common::File in;
|
|
// Read textBase
|
|
if (!in.open(getFileName(GAME_TEXTFILE))) {
|
|
error("loadGamePcFile: Can't load textbase file '%s'", getFileName(GAME_TEXTFILE));
|
|
}
|
|
|
|
_textBaseSize = in.size();
|
|
_textBase = (byte *)malloc(_textBaseSize);
|
|
if (_textBase == nullptr)
|
|
error("loadGamePcFile: Out of memory for textBase");
|
|
in.read(_textBase, _textBaseSize);
|
|
|
|
if (_textBase[getlong(30L)] != 128)
|
|
error("Unknown compression format");
|
|
}
|
|
}
|
|
|
|
void AGOSEngine::loadGamePcFile() {
|
|
int fileSize;
|
|
|
|
if (getFileName(GAME_BASEFILE) != nullptr) {
|
|
/* Read main gamexx file */
|
|
Common::File in;
|
|
if (!in.open(getFileName(GAME_BASEFILE))) {
|
|
error("loadGamePcFile: Can't load gamexx file '%s'", getFileName(GAME_BASEFILE));
|
|
}
|
|
|
|
if (getFeatures() & GF_CRUNCHED_GAMEPC) {
|
|
uint srcSize = in.size();
|
|
byte *srcBuf = (byte *)malloc(srcSize);
|
|
in.read(srcBuf, srcSize);
|
|
|
|
uint dstSize = READ_BE_UINT32(srcBuf + srcSize - 4);
|
|
byte *dstBuf = (byte *)malloc(dstSize);
|
|
decrunchFile(srcBuf, dstBuf, srcSize);
|
|
free(srcBuf);
|
|
|
|
Common::MemoryReadStream stream(dstBuf, dstSize);
|
|
readGamePcFile(&stream);
|
|
free(dstBuf);
|
|
} else {
|
|
readGamePcFile(&in);
|
|
}
|
|
}
|
|
|
|
if (getFileName(GAME_TBLFILE) != nullptr) {
|
|
/* Read list of TABLE resources */
|
|
Common::File in;
|
|
if (!in.open(getFileName(GAME_TBLFILE))) {
|
|
error("loadGamePcFile: Can't load table resources file '%s'", getFileName(GAME_TBLFILE));
|
|
}
|
|
|
|
fileSize = in.size();
|
|
|
|
_tblList = (byte *)malloc(fileSize);
|
|
if (_tblList == nullptr)
|
|
error("loadGamePcFile: Out of memory for strip table list");
|
|
in.read(_tblList, fileSize);
|
|
|
|
/* Remember the current state */
|
|
_subroutineListOrg = _subroutineList;
|
|
_tablesHeapPtrOrg = _tablesHeapPtr;
|
|
_tablesHeapCurPosOrg = _tablesHeapCurPos;
|
|
}
|
|
|
|
if (getFileName(GAME_STRFILE) != nullptr) {
|
|
/* Read list of TEXT resources */
|
|
Common::File in;
|
|
if (!in.open(getFileName(GAME_STRFILE)))
|
|
error("loadGamePcFile: Can't load text resources file '%s'", getFileName(GAME_STRFILE));
|
|
|
|
fileSize = in.size();
|
|
_strippedTxtMem = (byte *)malloc(fileSize);
|
|
if (_strippedTxtMem == nullptr)
|
|
error("loadGamePcFile: Out of memory for strip text list");
|
|
in.read(_strippedTxtMem, fileSize);
|
|
}
|
|
|
|
if (getFileName(GAME_STATFILE) != nullptr) {
|
|
/* Read list of ROOM STATE resources */
|
|
Common::File in;
|
|
if (!in.open(getFileName(GAME_STATFILE))) {
|
|
error("loadGamePcFile: Can't load state resources file '%s'", getFileName(GAME_STATFILE));
|
|
}
|
|
|
|
_numRoomStates = in.size() / 8;
|
|
|
|
_roomStates = (RoomState *)calloc(_numRoomStates, sizeof(RoomState));
|
|
if (_roomStates == nullptr)
|
|
error("loadGamePcFile: Out of memory for room state list");
|
|
|
|
for (uint s = 0; s < _numRoomStates; s++) {
|
|
uint16 num = in.readUint16BE() - (_itemArrayInited - 2);
|
|
|
|
_roomStates[num].state = in.readUint16BE();
|
|
_roomStates[num].classFlags = in.readUint16BE();
|
|
_roomStates[num].roomExitStates = in.readUint16BE();
|
|
}
|
|
}
|
|
|
|
if (getFileName(GAME_RMSLFILE) != nullptr) {
|
|
/* Read list of ROOM ITEMS resources */
|
|
Common::File in;
|
|
if (!in.open(getFileName(GAME_RMSLFILE))) {
|
|
error("loadGamePcFile: Can't load room resources file '%s'", getFileName(GAME_RMSLFILE));
|
|
}
|
|
|
|
fileSize = in.size();
|
|
|
|
_roomsList = (byte *)malloc(fileSize);
|
|
if (_roomsList == nullptr)
|
|
error("loadGamePcFile: Out of memory for room items list");
|
|
in.read(_roomsList, fileSize);
|
|
}
|
|
|
|
if (getFileName(GAME_XTBLFILE) != nullptr) {
|
|
/* Read list of XTABLE resources */
|
|
Common::File in;
|
|
if (!in.open(getFileName(GAME_XTBLFILE))) {
|
|
error("loadGamePcFile: Can't load xtable resources file '%s'", getFileName(GAME_XTBLFILE));
|
|
}
|
|
|
|
fileSize = in.size();
|
|
|
|
_xtblList = (byte *)malloc(fileSize);
|
|
if (_xtblList == nullptr)
|
|
error("loadGamePcFile: Out of memory for strip xtable list");
|
|
in.read(_xtblList, fileSize);
|
|
|
|
/* Remember the current state */
|
|
_xsubroutineListOrg = _subroutineList;
|
|
_xtablesHeapPtrOrg = _tablesHeapPtr;
|
|
_xtablesHeapCurPosOrg = _tablesHeapCurPos;
|
|
}
|
|
}
|
|
|
|
void AGOSEngine::readGamePcFile(Common::SeekableReadStream *in) {
|
|
int num_inited_objects;
|
|
int i;
|
|
|
|
num_inited_objects = allocGamePcVars(in);
|
|
|
|
createPlayer();
|
|
readGamePcText(in);
|
|
|
|
for (i = 2; i < num_inited_objects; i++) {
|
|
readItemFromGamePc(in, _itemArrayPtr[i]);
|
|
}
|
|
|
|
readSubroutineBlock(in);
|
|
}
|
|
|
|
void AGOSEngine::readGamePcText(Common::SeekableReadStream *in) {
|
|
_textSize = in->readUint32BE();
|
|
_textMem = (byte *)malloc(_textSize);
|
|
if (_textMem == nullptr)
|
|
error("readGamePcText: Out of text memory");
|
|
|
|
in->read(_textMem, _textSize);
|
|
|
|
setupStringTable(_textMem, _stringTabNum);
|
|
}
|
|
|
|
void AGOSEngine::readItemFromGamePc(Common::SeekableReadStream *in, Item *item) {
|
|
uint32 type;
|
|
|
|
if (getGameType() == GType_ELVIRA1) {
|
|
item->itemName = (uint16)in->readUint32BE();
|
|
item->adjective = in->readUint16BE();
|
|
item->noun = in->readUint16BE();
|
|
item->state = in->readUint16BE();
|
|
in->readUint16BE();
|
|
item->next = (uint16)fileReadItemID(in);
|
|
item->child = (uint16)fileReadItemID(in);
|
|
item->parent = (uint16)fileReadItemID(in);
|
|
in->readUint16BE();
|
|
in->readUint16BE();
|
|
in->readUint16BE();
|
|
item->classFlags = in->readUint16BE();
|
|
item->children = nullptr;
|
|
} else if (getGameType() == GType_ELVIRA2) {
|
|
item->itemName = (uint16)in->readUint32BE();
|
|
item->adjective = in->readUint16BE();
|
|
item->noun = in->readUint16BE();
|
|
item->state = in->readUint16BE();
|
|
item->next = (uint16)fileReadItemID(in);
|
|
item->child = (uint16)fileReadItemID(in);
|
|
item->parent = (uint16)fileReadItemID(in);
|
|
in->readUint16BE();
|
|
item->classFlags = in->readUint16BE();
|
|
item->children = nullptr;
|
|
} else {
|
|
item->adjective = in->readUint16BE();
|
|
item->noun = in->readUint16BE();
|
|
item->state = in->readUint16BE();
|
|
item->next = (uint16)fileReadItemID(in);
|
|
item->child = (uint16)fileReadItemID(in);
|
|
item->parent = (uint16)fileReadItemID(in);
|
|
in->readUint16BE();
|
|
item->classFlags = in->readUint16BE();
|
|
item->children = nullptr;
|
|
}
|
|
|
|
|
|
type = in->readUint32BE();
|
|
while (type) {
|
|
type = in->readUint16BE();
|
|
if (type != 0)
|
|
readItemChildren(in, item, type);
|
|
}
|
|
}
|
|
|
|
void AGOSEngine::readItemChildren(Common::SeekableReadStream *in, Item *item, uint type) {
|
|
if (type == kRoomType) {
|
|
SubRoom *subRoom = (SubRoom *)allocateChildBlock(item, kRoomType, sizeof(SubRoom));
|
|
subRoom->roomShort = in->readUint32BE();
|
|
subRoom->roomLong = in->readUint32BE();
|
|
subRoom->flags = in->readUint16BE();
|
|
} else if (type == kObjectType) {
|
|
SubObject *subObject = (SubObject *)allocateChildBlock(item, kObjectType, sizeof(SubObject));
|
|
in->readUint32BE();
|
|
in->readUint32BE();
|
|
in->readUint32BE();
|
|
subObject->objectName = in->readUint32BE();
|
|
subObject->objectSize = in->readUint16BE();
|
|
subObject->objectWeight = in->readUint16BE();
|
|
subObject->objectFlags = in->readUint16BE();
|
|
} else if (type == kGenExitType) {
|
|
SubGenExit *genExit = (SubGenExit *)allocateChildBlock(item, kGenExitType, sizeof(SubGenExit));
|
|
genExit->dest[0] = (uint16)fileReadItemID(in);
|
|
genExit->dest[1] = (uint16)fileReadItemID(in);
|
|
genExit->dest[2] = (uint16)fileReadItemID(in);
|
|
genExit->dest[3] = (uint16)fileReadItemID(in);
|
|
genExit->dest[4] = (uint16)fileReadItemID(in);
|
|
genExit->dest[5] = (uint16)fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
} else if (type == kContainerType) {
|
|
SubContainer *container = (SubContainer *)allocateChildBlock(item, kContainerType, sizeof(SubContainer));
|
|
container->volume = in->readUint16BE();
|
|
container->flags = in->readUint16BE();
|
|
} else if (type == kChainType) {
|
|
SubChain *chain = (SubChain *)allocateChildBlock(item, kChainType, sizeof(SubChain));
|
|
chain->chChained = (uint16)fileReadItemID(in);
|
|
} else if (type == kUserFlagType) {
|
|
setUserFlag(item, 0, in->readUint16BE());
|
|
setUserFlag(item, 1, in->readUint16BE());
|
|
setUserFlag(item, 2, in->readUint16BE());
|
|
setUserFlag(item, 3, in->readUint16BE());
|
|
setUserFlag(item, 4, in->readUint16BE());
|
|
setUserFlag(item, 5, in->readUint16BE());
|
|
setUserFlag(item, 6, in->readUint16BE());
|
|
setUserFlag(item, 7, in->readUint16BE());
|
|
SubUserFlag *subUserFlag = (SubUserFlag *)findChildOfType(item, kUserFlagType);
|
|
subUserFlag->userItems[0] = (uint16)fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
fileReadItemID(in);
|
|
} else if (type == kInheritType) {
|
|
SubInherit *inherit = (SubInherit *)allocateChildBlock(item, kInheritType, sizeof(SubInherit));
|
|
inherit->inMaster = (uint16)fileReadItemID(in);
|
|
} else {
|
|
error("readItemChildren: invalid type %d", type);
|
|
}
|
|
}
|
|
|
|
void AGOSEngine_Elvira2::readItemChildren(Common::SeekableReadStream *in, Item *item, uint type) {
|
|
if (type == kRoomType) {
|
|
uint fr1 = in->readUint16BE();
|
|
uint fr2 = in->readUint16BE();
|
|
uint i, size;
|
|
uint j, k;
|
|
SubRoom *subRoom;
|
|
|
|
size = SubRoom_SIZE;
|
|
for (i = 0, j = fr2; i != 6; i++, j >>= 2)
|
|
if (j & 3)
|
|
size += sizeof(subRoom->roomExit[0]);
|
|
|
|
subRoom = (SubRoom *)allocateChildBlock(item, kRoomType, size);
|
|
subRoom->subroutine_id = fr1;
|
|
subRoom->roomExitStates = fr2;
|
|
|
|
for (i = k = 0, j = fr2; i != 6; i++, j >>= 2)
|
|
if (j & 3)
|
|
subRoom->roomExit[k++] = (uint16)fileReadItemID(in);
|
|
} else if (type == kObjectType) {
|
|
uint32 fr = in->readUint32BE();
|
|
uint i, k, size;
|
|
SubObject *subObject;
|
|
|
|
size = SubObject_SIZE;
|
|
for (i = 0; i != 16; i++)
|
|
if (fr & (1 << i))
|
|
size += sizeof(subObject->objectFlagValue[0]);
|
|
|
|
subObject = (SubObject *)allocateChildBlock(item, kObjectType, size);
|
|
subObject->objectFlags = fr;
|
|
|
|
k = 0;
|
|
if (fr & 1) {
|
|
subObject->objectFlagValue[k++] = (uint16)in->readUint32BE();
|
|
}
|
|
for (i = 1; i != 16; i++)
|
|
if (fr & (1 << i))
|
|
subObject->objectFlagValue[k++] = in->readUint16BE();
|
|
|
|
if (getGameType() != GType_ELVIRA2)
|
|
subObject->objectName = (uint16)in->readUint32BE();
|
|
} else if (type == kSuperRoomType) {
|
|
assert(getGameType() == GType_ELVIRA2);
|
|
|
|
uint i, j, k, size;
|
|
uint id, x, y, z;
|
|
SubSuperRoom *subSuperRoom;
|
|
|
|
id = in->readUint16BE();
|
|
x = in->readUint16BE();
|
|
y = in->readUint16BE();
|
|
z = in->readUint16BE();
|
|
|
|
j = x * y * z;
|
|
size = SubSuperRoom_SIZE;
|
|
for (i = 0; i != j; i++)
|
|
size += sizeof(subSuperRoom->roomExitStates[0]);
|
|
|
|
subSuperRoom = (SubSuperRoom *)allocateChildBlock(item, kSuperRoomType, size);
|
|
subSuperRoom->subroutine_id = id;
|
|
subSuperRoom->roomX = x;
|
|
subSuperRoom->roomY = y;
|
|
subSuperRoom->roomZ = z;
|
|
|
|
for (i = k = 0; i != j; i++)
|
|
subSuperRoom->roomExitStates[k++] = in->readUint16BE();
|
|
} else if (type == kContainerType) {
|
|
SubContainer *container = (SubContainer *)allocateChildBlock(item, kContainerType, sizeof(SubContainer));
|
|
container->volume = in->readUint16BE();
|
|
container->flags = in->readUint16BE();
|
|
} else if (type == kChainType) {
|
|
SubChain *chain = (SubChain *)allocateChildBlock(item, kChainType, sizeof(SubChain));
|
|
chain->chChained = (uint16)fileReadItemID(in);
|
|
} else if (type == kUserFlagType) {
|
|
setUserFlag(item, 0, in->readUint16BE());
|
|
setUserFlag(item, 1, in->readUint16BE());
|
|
setUserFlag(item, 2, in->readUint16BE());
|
|
setUserFlag(item, 3, in->readUint16BE());
|
|
} else if (type == kInheritType) {
|
|
SubInherit *inherit = (SubInherit *)allocateChildBlock(item, kInheritType, sizeof(SubInherit));
|
|
inherit->inMaster = (uint16)fileReadItemID(in);
|
|
} else {
|
|
error("readItemChildren: invalid type %d", type);
|
|
}
|
|
}
|
|
|
|
uint fileReadItemID(Common::SeekableReadStream *in) {
|
|
uint32 val = in->readUint32BE();
|
|
if (val == 0xFFFFFFFF)
|
|
return 0;
|
|
return val + 2;
|
|
}
|
|
|
|
void AGOSEngine::openGameFile() {
|
|
_gameFile = new Common::File();
|
|
_gameFile->open(getFileName(GAME_GMEFILE));
|
|
|
|
if (!_gameFile->isOpen())
|
|
error("openGameFile: Can't load game file '%s'", getFileName(GAME_GMEFILE));
|
|
|
|
uint32 size = _gameFile->readUint32LE();
|
|
|
|
_gameOffsetsPtr = (uint32 *)malloc(size);
|
|
if (_gameOffsetsPtr == nullptr)
|
|
error("openGameFile: Out of memory, game offsets");
|
|
|
|
_gameFile->seek(0, SEEK_SET);
|
|
|
|
for (uint r = 0; r < size / 4; r++)
|
|
_gameOffsetsPtr[r] = _gameFile->readUint32LE();
|
|
}
|
|
|
|
void AGOSEngine::readGameFile(void *dst, uint32 offs, uint32 size) {
|
|
_gameFile->seek(offs, SEEK_SET);
|
|
if (_gameFile->read(dst, size) != size)
|
|
error("readGameFile: Read failed (%d,%d)", offs, size);
|
|
}
|
|
|
|
// Thanks to Stuart Caie for providing the original
|
|
// C conversion upon which this decruncher is based.
|
|
|
|
#define SD_GETBIT(var) do { \
|
|
if (!bits--) { \
|
|
s -= 4; \
|
|
if (s < src) \
|
|
return false; \
|
|
bb = READ_BE_UINT32(s); \
|
|
bits = 31; \
|
|
} \
|
|
(var) = bb & 1; \
|
|
bb >>= 1; \
|
|
}while (0)
|
|
|
|
#define SD_GETBITS(var, nbits) do { \
|
|
bc = (nbits); \
|
|
(var) = 0; \
|
|
while (bc--) { \
|
|
(var) <<= 1; \
|
|
SD_GETBIT(bit); \
|
|
(var) |= bit; \
|
|
} \
|
|
}while (0)
|
|
|
|
#define SD_TYPE_LITERAL (0)
|
|
#define SD_TYPE_MATCH (1)
|
|
|
|
bool AGOSEngine::decrunchFile(byte *src, byte *dst, uint32 size) {
|
|
byte *s = src + size - 4;
|
|
uint32 destlen = READ_BE_UINT32 (s);
|
|
uint32 bb, x, y;
|
|
byte *d = dst + destlen;
|
|
byte bc, bit, bits, type;
|
|
|
|
// Initialize bit buffer.
|
|
s -= 4;
|
|
bb = x = READ_BE_UINT32 (s);
|
|
bits = 0;
|
|
do {
|
|
x >>= 1;
|
|
bits++;
|
|
} while (x);
|
|
bits--;
|
|
|
|
while (d > dst) {
|
|
SD_GETBIT(x);
|
|
if (x) {
|
|
SD_GETBITS(x, 2);
|
|
switch (x) {
|
|
case 0:
|
|
type = SD_TYPE_MATCH;
|
|
x = 9;
|
|
y = 2;
|
|
break;
|
|
|
|
case 1:
|
|
type = SD_TYPE_MATCH;
|
|
x = 10;
|
|
y = 3;
|
|
break;
|
|
|
|
case 2:
|
|
type = SD_TYPE_MATCH;
|
|
x = 12;
|
|
SD_GETBITS(y, 8);
|
|
break;
|
|
|
|
default:
|
|
type = SD_TYPE_LITERAL;
|
|
x = 8;
|
|
y = 8;
|
|
}
|
|
} else {
|
|
SD_GETBIT(x);
|
|
if (x) {
|
|
type = SD_TYPE_MATCH;
|
|
x = 8;
|
|
y = 1;
|
|
} else {
|
|
type = SD_TYPE_LITERAL;
|
|
x = 3;
|
|
y = 0;
|
|
}
|
|
}
|
|
|
|
if (type == SD_TYPE_LITERAL) {
|
|
SD_GETBITS(x, x);
|
|
y += x;
|
|
if ((int)(y + 1) > (d - dst))
|
|
return false; // Overflow?
|
|
do {
|
|
SD_GETBITS(x, 8);
|
|
*--d = x;
|
|
} while (y-- > 0);
|
|
} else {
|
|
if ((int)(y + 1) > (d - dst))
|
|
return false; // Overflow?
|
|
SD_GETBITS(x, x);
|
|
if ((d + x) > (dst + destlen))
|
|
return false; // Offset overflow?
|
|
do {
|
|
d--;
|
|
*d = d[x];
|
|
} while (y-- > 0);
|
|
}
|
|
}
|
|
|
|
// Successful decrunch.
|
|
return true;
|
|
}
|
|
|
|
#undef SD_GETBIT
|
|
#undef SD_GETBITS
|
|
#undef SD_TYPE_LITERAL
|
|
#undef SD_TYPE_MATCH
|
|
|
|
static bool getBit(Common::Stack<uint32> &dataList, uint32 &srcVal) {
|
|
bool result = srcVal & 1;
|
|
srcVal >>= 1;
|
|
if (srcVal == 0) {
|
|
srcVal = dataList.pop();
|
|
|
|
result = srcVal & 1;
|
|
srcVal = (srcVal >> 1) | 0x80000000L;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static uint32 copyBits(Common::Stack<uint32> &dataList, uint32 &srcVal, int numBits) {
|
|
uint32 destVal = 0;
|
|
|
|
for (int i = 0; i < numBits; ++i) {
|
|
bool f = getBit(dataList, srcVal);
|
|
destVal = (destVal << 1) | (f ? 1 : 0);
|
|
}
|
|
|
|
return destVal;
|
|
}
|
|
|
|
static void transferLoop(uint8 *dataOut, int &outIndex, uint32 destVal, int max) {
|
|
assert(outIndex > max - 1);
|
|
byte *pDest = dataOut + outIndex;
|
|
|
|
for (int i = 0; (i <= max) && (outIndex > 0); ++i) {
|
|
pDest = dataOut + --outIndex;
|
|
*pDest = pDest[destVal];
|
|
}
|
|
}
|
|
|
|
void AGOSEngine::decompressPN(Common::Stack<uint32> &dataList, uint8 *&dataOut, int &dataOutSize) {
|
|
// Set up the output data area
|
|
dataOutSize = dataList.pop();
|
|
dataOut = new uint8[dataOutSize];
|
|
int outIndex = dataOutSize;
|
|
|
|
// Decompression routine
|
|
uint32 srcVal = dataList.pop();
|
|
uint32 destVal;
|
|
|
|
while (outIndex > 0) {
|
|
uint32 numBits = 0;
|
|
int count = 0;
|
|
|
|
if (getBit(dataList, srcVal)) {
|
|
destVal = copyBits(dataList, srcVal, 2);
|
|
|
|
if (destVal < 2) {
|
|
count = destVal + 2;
|
|
destVal = copyBits(dataList, srcVal, destVal + 9);
|
|
transferLoop(dataOut, outIndex, destVal, count);
|
|
continue;
|
|
} else if (destVal != 3) {
|
|
count = copyBits(dataList, srcVal, 8);
|
|
destVal = copyBits(dataList, srcVal, 8);
|
|
transferLoop(dataOut, outIndex, destVal, count);
|
|
continue;
|
|
} else {
|
|
numBits = 8;
|
|
count = 8;
|
|
}
|
|
} else if (getBit(dataList, srcVal)) {
|
|
destVal = copyBits(dataList, srcVal, 8);
|
|
transferLoop(dataOut, outIndex, destVal, 1);
|
|
continue;
|
|
} else {
|
|
numBits = 3;
|
|
count = 0;
|
|
}
|
|
|
|
destVal = copyBits(dataList, srcVal, numBits);
|
|
count += destVal;
|
|
|
|
// Loop through extracting specified number of bytes
|
|
for (int i = 0; i <= count; ++i) {
|
|
// Shift 8 bits from the source to the destination
|
|
for (int bitCtr = 0; bitCtr < 8; ++bitCtr) {
|
|
bool flag = getBit(dataList, srcVal);
|
|
destVal = (destVal << 1) | (flag ? 1 : 0);
|
|
}
|
|
|
|
dataOut[--outIndex] = destVal & 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AGOSEngine::loadVGABeardFile(uint16 id) {
|
|
uint32 offs, size;
|
|
|
|
if (getFeatures() & GF_OLD_BUNDLE) {
|
|
Common::File in;
|
|
char filename[15];
|
|
if (id == 23)
|
|
id = 112;
|
|
else if (id == 328)
|
|
id = 119;
|
|
|
|
if (getPlatform() == Common::kPlatformAmiga) {
|
|
if (getFeatures() & GF_TALKIE)
|
|
sprintf(filename, "0%d.out", id);
|
|
else
|
|
sprintf(filename, "0%d.pkd", id);
|
|
} else {
|
|
sprintf(filename, "0%d.VGA", id);
|
|
}
|
|
|
|
if (!in.open(filename))
|
|
error("loadSimonVGAFile: Can't load %s", filename);
|
|
|
|
size = in.size();
|
|
if (getFeatures() & GF_CRUNCHED) {
|
|
byte *srcBuffer = (byte *)malloc(size);
|
|
if (in.read(srcBuffer, size) != size)
|
|
error("loadSimonVGAFile: Read failed");
|
|
decrunchFile(srcBuffer, _vgaBufferPointers[11].vgaFile2, size);
|
|
free(srcBuffer);
|
|
} else {
|
|
if (in.read(_vgaBufferPointers[11].vgaFile2, size) != size)
|
|
error("loadSimonVGAFile: Read failed");
|
|
}
|
|
} else {
|
|
offs = _gameOffsetsPtr[id];
|
|
size = _gameOffsetsPtr[id + 1] - offs;
|
|
readGameFile(_vgaBufferPointers[11].vgaFile2, offs, size);
|
|
}
|
|
}
|
|
|
|
uint8 safeReadByte(const uint8 *&src, const uint8 *end) {
|
|
if (src < end)
|
|
return (*src++);
|
|
error("decodePak98(): invalid data");
|
|
return 0;
|
|
}
|
|
|
|
uint16 safeReadWord(const uint8 *&src, const uint8 *end) {
|
|
uint8 lo = safeReadByte(src, end);
|
|
return (safeReadByte(src, end) << 8) | lo;
|
|
}
|
|
|
|
#define S_NEXTBYTE safeReadByte(src, endSrc)
|
|
#define S_NEXTWORD safeReadWord(src, endSrc)
|
|
|
|
void decodePak98(uint8 *dst, uint32 outSize, const uint8 *src, uint32 inSize) {
|
|
const uint8 *end = dst + outSize;
|
|
const uint8 *endSrc = src + inSize;
|
|
uint8 state = 0x80;
|
|
uint8 flg = S_NEXTBYTE;
|
|
|
|
for (uint32 srcBytesLeft = inSize - 1; srcBytesLeft; ) {
|
|
if (state & flg) {
|
|
if (dst < end)
|
|
*dst++ = S_NEXTBYTE;
|
|
--srcBytesLeft;
|
|
} else {
|
|
srcBytesLeft -= 2;
|
|
uint16 cmd2 = S_NEXTWORD;
|
|
uint8 cmd3 = cmd2 & 0x0F;
|
|
cmd2 >>= 4;
|
|
|
|
if (cmd2 == 0) {
|
|
uint16 count = cmd3 + 4;
|
|
--srcBytesLeft;
|
|
if (cmd3 == 0x0F) {
|
|
count = S_NEXTWORD;
|
|
srcBytesLeft -= 2;
|
|
} else if (cmd3 == 0x0E) {
|
|
count = 18 + (S_NEXTBYTE);
|
|
--srcBytesLeft;
|
|
}
|
|
|
|
uint8 destVal = S_NEXTBYTE;
|
|
while (count-- && dst < end)
|
|
*dst++ = destVal;
|
|
|
|
} else if (cmd2 == 1) {
|
|
uint16 count = cmd3 + 3;
|
|
if (cmd3 == 0x0F) {
|
|
count = S_NEXTWORD;
|
|
srcBytesLeft -= 2;
|
|
} else if (cmd3 == 0x0E) {
|
|
count = 17 + (S_NEXTBYTE);
|
|
--srcBytesLeft;
|
|
}
|
|
|
|
dst += count;
|
|
|
|
} else if (cmd2 == 2) {
|
|
uint16 count = cmd3 + 16;
|
|
if (cmd3 == 0x0F) {
|
|
count = S_NEXTWORD;
|
|
srcBytesLeft -= 2;
|
|
} else if (cmd3 == 0x0E) {
|
|
count = 30 + (S_NEXTBYTE);
|
|
--srcBytesLeft;
|
|
}
|
|
|
|
srcBytesLeft -= count;
|
|
while (count-- && dst < end)
|
|
*dst++ = S_NEXTBYTE;
|
|
|
|
} else {
|
|
uint16 count = cmd3 + 3;
|
|
if (cmd3 == 0x0F) {
|
|
count = 18 + (S_NEXTBYTE);
|
|
--srcBytesLeft;
|
|
}
|
|
|
|
const uint8 *src2 = dst - cmd2;
|
|
while (count-- && dst < end)
|
|
*dst++ = *src2++;
|
|
}
|
|
}
|
|
|
|
if (!(state >>= 1)) {
|
|
state = 0x80;
|
|
if (srcBytesLeft) {
|
|
flg = S_NEXTBYTE;
|
|
srcBytesLeft--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef S_NEXTBYTE
|
|
#undef S_NEXTWORD
|
|
|
|
void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
|
|
Common::File in;
|
|
char filename[15];
|
|
byte *dst;
|
|
uint32 file, offs, srcSize, dstSize;
|
|
uint extraBuffer = 0;
|
|
|
|
if ((getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) &&
|
|
id == 2 && type == 2) {
|
|
// WORKAROUND: For the extra long strings in foreign languages
|
|
// Allocate more space for text to cope with foreign languages that use
|
|
// up more space than English. I hope 6400 bytes are enough. This number
|
|
// is base on: 2 (lines) * 320 (screen width) * 10 (textheight) -- olki
|
|
extraBuffer += 6400;
|
|
}
|
|
|
|
if (getFeatures() & GF_ZLIBCOMP) {
|
|
loadOffsets(getFileName(GAME_GFXIDXFILE), id * 3 + type, file, offs, srcSize, dstSize);
|
|
|
|
if (getPlatform() == Common::kPlatformAmiga)
|
|
sprintf(filename, "GFX%d.VGA", file);
|
|
else
|
|
sprintf(filename, "graphics.vga");
|
|
|
|
dst = allocBlock(dstSize + extraBuffer);
|
|
decompressData(filename, dst, offs, srcSize, dstSize);
|
|
} else if (getFeatures() & GF_OLD_BUNDLE) {
|
|
if (getPlatform() == Common::kPlatformAcorn) {
|
|
sprintf(filename, "%.3d%d.DAT", id, type);
|
|
} else if (getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) {
|
|
if (getFeatures() & GF_TALKIE) {
|
|
sprintf(filename, "%.3d%d.out", id, type);
|
|
} else if (getGameType() == GType_ELVIRA1 && getFeatures() & GF_DEMO) {
|
|
if (getPlatform() == Common::kPlatformAtariST)
|
|
sprintf(filename, "%.2d%d.out", id, type);
|
|
else
|
|
sprintf(filename, "%c%d.out", 48 + id, type);
|
|
} else if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2) {
|
|
sprintf(filename, "%.2d%d.pkd", id, type);
|
|
} else if (getGameType() == GType_PN) {
|
|
sprintf(filename, "%c%d.in", id + 48, type);
|
|
} else {
|
|
sprintf(filename, "%.3d%d.pkd", id, type);
|
|
}
|
|
} else {
|
|
if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
|
|
sprintf(filename, "%.2d.GR2", id);
|
|
} else if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
|
|
sprintf(filename, "%.2d%d.VGA", id, type);
|
|
} else if (getGameType() == GType_PN) {
|
|
sprintf(filename, "%c%d.out", id + 48, type);
|
|
} else {
|
|
sprintf(filename, "%.3d%d.VGA", id, type);
|
|
}
|
|
}
|
|
|
|
if (!in.open(filename)) {
|
|
if (useError)
|
|
error("loadVGAVideoFile: Can't load %s", filename);
|
|
|
|
_block = _blockEnd = nullptr;
|
|
return;
|
|
}
|
|
|
|
dstSize = srcSize = in.size();
|
|
if (getGameType() == GType_PN && getPlatform() == Common::kPlatformDOS && id == 17 && type == 2) {
|
|
// The A2.out file isn't compressed in PC version of Personal Nightmare
|
|
dst = allocBlock(dstSize + extraBuffer);
|
|
if (in.read(dst, dstSize) != dstSize)
|
|
error("loadVGAVideoFile: Read failed");
|
|
} else if (getGameType() == GType_PN && (getFeatures() & GF_CRUNCHED)) {
|
|
Common::Stack<uint32> data;
|
|
byte *dataOut = nullptr;
|
|
int dataOutSize = 0;
|
|
|
|
for (uint i = 0; i < srcSize / 4; ++i) {
|
|
uint32 dataVal = in.readUint32BE();
|
|
// Correct incorrect byte, in corrupt 72.out file, included in some PC versions.
|
|
if (dataVal == 168042714)
|
|
data.push(168050906);
|
|
else
|
|
data.push(dataVal);
|
|
}
|
|
|
|
decompressPN(data, dataOut, dataOutSize);
|
|
dst = allocBlock (dataOutSize + extraBuffer);
|
|
memcpy(dst, dataOut, dataOutSize);
|
|
delete[] dataOut;
|
|
} else if (getFeatures() & GF_CRUNCHED) {
|
|
byte *srcBuffer = (byte *)malloc(srcSize);
|
|
if (in.read(srcBuffer, srcSize) != srcSize)
|
|
error("loadVGAVideoFile: Read failed");
|
|
|
|
dstSize = READ_BE_UINT32(srcBuffer + srcSize - 4);
|
|
dst = allocBlock (dstSize + extraBuffer);
|
|
decrunchFile(srcBuffer, dst, srcSize);
|
|
free(srcBuffer);
|
|
} else if (getPlatform() == Common::kPlatformPC98) {
|
|
bool compressed = (in.readUint16LE() == 1);
|
|
srcSize = in.readUint32LE();
|
|
if (type == 1) {
|
|
if (compressed)
|
|
srcSize = in.readUint32LE() + 2;
|
|
in.seek(srcSize, SEEK_CUR);
|
|
compressed = (in.readUint16LE() == 1);
|
|
srcSize = in.readUint32LE();
|
|
}
|
|
|
|
if (compressed) {
|
|
dstSize = srcSize;
|
|
srcSize = in.readUint32LE();
|
|
uint16 fill = in.readUint16LE();
|
|
dst = allocBlock(dstSize);
|
|
|
|
Common::fill<uint16*, uint16>((uint16*)dst, (uint16*)(dst + (dstSize & ~1)), TO_LE_16(fill));
|
|
if (dstSize & 1)
|
|
*(dst + dstSize - 1) = fill & 0xff;
|
|
|
|
if (srcSize) {
|
|
uint8 *srcBuffer = new uint8[srcSize];
|
|
if (in.read(srcBuffer, srcSize) != srcSize)
|
|
error("loadVGAVideoFile: Read failed");
|
|
decodePak98(dst, dstSize, srcBuffer, srcSize);
|
|
delete[] srcBuffer;
|
|
}
|
|
} else {
|
|
dstSize = srcSize;
|
|
dst = allocBlock(dstSize + extraBuffer);
|
|
if (in.read(dst, dstSize) != dstSize)
|
|
error("loadVGAVideoFile: Read failed");
|
|
}
|
|
} else {
|
|
dst = allocBlock(dstSize + extraBuffer);
|
|
if (in.read(dst, dstSize) != dstSize)
|
|
error("loadVGAVideoFile: Read failed");
|
|
}
|
|
} else {
|
|
id = id * 2 + (type - 1);
|
|
offs = _gameOffsetsPtr[id];
|
|
dstSize = _gameOffsetsPtr[id + 1] - offs;
|
|
|
|
if (!dstSize) {
|
|
if (useError)
|
|
error("loadVGAVideoFile: Can't load id %d type %d", id, type);
|
|
|
|
_block = _blockEnd = nullptr;
|
|
return;
|
|
}
|
|
|
|
dst = allocBlock(dstSize + extraBuffer);
|
|
readGameFile(dst, offs, dstSize);
|
|
}
|
|
}
|
|
|
|
Common::SeekableReadStream *AGOSEngine::createPak98FileStream(const char *filename) {
|
|
Common::File in;
|
|
if (!in.open(filename))
|
|
return nullptr;
|
|
|
|
/*uint16 cmpType = */in.readUint16LE();
|
|
uint32 outSize = in.readUint32LE();
|
|
uint32 inSize = in.readUint32LE();
|
|
uint16 fill = in.readUint16LE();
|
|
|
|
uint8 *decBuffer = (uint8*)malloc(outSize);
|
|
Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), TO_LE_16(fill));
|
|
if (outSize & 1)
|
|
*(decBuffer + outSize - 1) = fill & 0xff;
|
|
|
|
if (inSize) {
|
|
uint8 *tempBuffer = new uint8[inSize];
|
|
in.read(tempBuffer, inSize);
|
|
decodePak98(decBuffer, outSize, tempBuffer, inSize);
|
|
delete[] tempBuffer;
|
|
}
|
|
|
|
return new Common::MemoryReadStream(decBuffer, outSize, DisposeAfterUse::YES);
|
|
}
|
|
|
|
void AGOSEngine::convertPC98Image(VC10_state &state) {
|
|
if (state.flags & (kDFCompressedFlip | kDFCompressed)) {
|
|
const byte *src = state.srcPtr;
|
|
uint32 outSize = READ_LE_UINT32(src + 2);
|
|
assert(outSize >= (uint32)((state.width << 3) * state.height));
|
|
uint32 inSize = READ_LE_UINT32(src + 6);
|
|
uint16 fill = READ_LE_UINT16(src + 10);
|
|
delete[] _pak98Buf;
|
|
byte *decBuffer = new uint8[outSize];
|
|
Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), TO_LE_16(fill));
|
|
if (outSize & 1)
|
|
*(decBuffer + outSize - 1) = fill & 0xff;
|
|
if (inSize)
|
|
decodePak98(decBuffer, outSize, src + 12, inSize);
|
|
_pak98Buf = state.srcPtr = decBuffer;
|
|
_paletteModNext = 0;
|
|
}
|
|
|
|
// The PC-98 images are in a planar format, but slightly different from the Amiga format. It does
|
|
// not make much sense to set the GF_PLANAR flag, since the Amiga code can't be used anyway.
|
|
free(_planarBuf);
|
|
uint16 planeLW = state.width << 1;
|
|
uint16 planePitch = planeLW * 3;
|
|
|
|
_planarBuf = (byte*)malloc((state.width << 3) * state.height);
|
|
|
|
const byte *src[4];
|
|
memset(src, 0, sizeof(src));
|
|
for (int i = 0; i < 4; ++i)
|
|
src[i] = state.srcPtr + i * planeLW;
|
|
byte *dst = _planarBuf;
|
|
|
|
for (int y = 0; y < state.height; ++y) {
|
|
for (int x = 0; x < planeLW; ++x) {
|
|
for (int i = 0; i <= 6; i += 2) {
|
|
byte col = 0;
|
|
for (int ii = 0; ii < 4; ++ii) {
|
|
col |= ((*src[ii] >> (7 - i)) & 1) << (ii + 4);
|
|
col |= ((*src[ii] >> (6 - i)) & 1) << ii;
|
|
}
|
|
*dst++ = col;
|
|
}
|
|
for (int ii = 0; ii < 4; ++ii)
|
|
++src[ii];
|
|
}
|
|
for (int ii = 0; ii < 4; ++ii)
|
|
src[ii] += planePitch;
|
|
}
|
|
|
|
state.srcPtr = _planarBuf;
|
|
if (state.flags & kDFCompressedFlip)
|
|
state.flags |= kDFFlip;
|
|
state.flags &= ~(kDFCompressedFlip | kDFCompressed);
|
|
}
|
|
|
|
} // End of namespace AGOS
|