scummvm/engines/agos/res.cpp
Le Philousophe 5bdd9e6f50 ALL: Unify zlib and gzio API
zlib is used when available and falls back on gzio.
This allows performance improvements as our CRC32 and gzio
implementations are slower than base zlib.
As zlib is available when libpng is present, this is sensible to
benefit from it.
2023-08-28 21:49:03 +02:00

1140 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/compression/deflate.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) {
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::inflateZlib(dst, &decompressedSize, srcBuffer, srcSize))
error("decompressData: Zlib uncompress error");
free(srcBuffer);
} else {
if (in.read(dst, dstSize) != dstSize)
error("decompressData: Read failed");
}
in.close();
}
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)
Common::sprintf_s(filename, "0%d.out", id);
else
Common::sprintf_s(filename, "0%d.pkd", id);
} else if (getPlatform() == Common::kPlatformAcorn) {
Common::sprintf_s(filename, "%.2d/0%d", id / 100, id);
} else {
Common::sprintf_s(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)
Common::sprintf_s(filename, "GFX%d.VGA", file);
else
Common::sprintf_s(filename, "graphics.vga");
dst = allocBlock(dstSize + extraBuffer);
decompressData(filename, dst, offs, srcSize, dstSize);
} else if (getFeatures() & GF_OLD_BUNDLE) {
if (getPlatform() == Common::kPlatformAcorn) {
Common::sprintf_s(filename, "%.2d/%.3d%d", id / 10, id, type);
} else if (getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) {
if (getFeatures() & GF_TALKIE) {
Common::sprintf_s(filename, "%.3d%d.out", id, type);
} else if (getGameType() == GType_ELVIRA1 && getFeatures() & GF_DEMO) {
if (getPlatform() == Common::kPlatformAtariST)
Common::sprintf_s(filename, "%.2d%d.out", id, type);
else
Common::sprintf_s(filename, "%c%d.out", 48 + id, type);
} else if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2) {
Common::sprintf_s(filename, "%.2d%d.pkd", id, type);
} else if (getGameType() == GType_PN) {
Common::sprintf_s(filename, "%c%d.in", id + 48, type);
} else {
Common::sprintf_s(filename, "%.3d%d.pkd", id, type);
}
} else {
if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
Common::sprintf_s(filename, "%.2d.GR2", id);
} else if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
Common::sprintf_s(filename, "%.2d%d.VGA", id, type);
} else if (getGameType() == GType_PN) {
Common::sprintf_s(filename, "%c%d.out", id + 48, type);
} else {
Common::sprintf_s(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