Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,564 @@
/* 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/>.
*
*/
#include "lastexpress/lastexpress.h"
#include "lastexpress/data/archive.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/memstream.h"
namespace LastExpress {
ArchiveManager::ArchiveManager(LastExpressEngine *engine) {
_engine = engine;
_cdFilePointer = new Common::File();
_hdFilePointer = new Common::File();
}
ArchiveManager::~ArchiveManager() {
SAFE_DELETE(_cdFilePointer);
SAFE_DELETE(_hdFilePointer);
}
HPF *ArchiveManager::search(const char *name, HPF *archive, int archiveSize) {
int left = 0;
int right = archiveSize - 1;
if (right < 0)
return nullptr;
while (left <= right) {
int mid = (left + right) / 2;
int compareResult = scumm_strnicmp(name, archive[mid].name, sizeof(archive[mid].name));
if (compareResult < 0) {
right = mid - 1;
} else if (compareResult > 0) {
left = mid + 1;
} else { // Found it!
return &archive[mid];
}
}
return nullptr;
}
bool ArchiveManager::lockCD(int32 index) {
char filename[80];
if (_cdFilePointer && _cdArchiveIndex == index)
return true;
// Not present in the original, but we need it since we're about to reopen a new file...
if (_cdFilePointer && _cdFilePointer->isOpen())
_cdFilePointer->close();
if (!isCDAvailable(index, filename, sizeof(filename)) || !lockCache(filename))
return false;
_cdArchiveIndex = index;
return true;
}
bool ArchiveManager::isCDAvailable(int cdNum, char *outPath, int pathSize) {
Common::sprintf_s(outPath, pathSize, "CD%d.hpf", cdNum);
return true;
}
bool ArchiveManager::lockCache(char *filename) {
uint32 remainingArchiveSize;
uint32 curSubFilesNum;
uint32 archiveIndex = 0;
_cdFilePosition = 0;
if (!_cdFilePointer || !_cdFilePointer->open(filename))
return false;
_cdArchiveNumFiles = _cdFilePointer->readUint32LE();
if (_cdFilePointer->err()) {
error("Error reading from file \"%s\"", filename);
}
remainingArchiveSize = _cdArchiveNumFiles;
_cdFilePosition += 4;
if (_cdArchiveNumFiles) {
do {
curSubFilesNum = 500;
if (remainingArchiveSize <= 500)
curSubFilesNum = remainingArchiveSize;
// The original just did a mass fread of curSubFilesNum elements, with 22 as element size.
// This is not quite portable as the bytes are directly associated to the struct, which might
// have alignment differences, so instead we do it in the "ScummVM way"...
for (uint i = 0; i < curSubFilesNum; i++) {
_cdFilePointer->read(_cdArchive[archiveIndex + i].name, sizeof(_cdArchive[archiveIndex + i].name));
_cdArchive[archiveIndex + i].offset = _cdFilePointer->readUint32LE();
_cdArchive[archiveIndex + i].size = _cdFilePointer->readUint16LE();
_cdArchive[archiveIndex + i].currentPos = _cdFilePointer->readUint16LE();
_cdArchive[archiveIndex + i].status = _cdFilePointer->readUint16LE();
}
if (_cdFilePointer->err()) {
error("Error reading from file \"%s\"", filename);
}
archiveIndex += curSubFilesNum;
remainingArchiveSize -= curSubFilesNum;
_engine->getSoundManager()->soundThread();
} while (remainingArchiveSize);
}
_cdFilePosition += 22 * _cdArchiveNumFiles;
return true;
}
void ArchiveManager::initHPFS() {
_hdFilePosition = 0;
if (!_hdFilePointer || !_hdFilePointer->open(_engine->isDemo() ? "DEMO.HPF" : "HD.hpf")) {
error("Hard drive cache not found (please reinstall)");
}
_cdArchive = (HPF *)malloc(6500 * sizeof(HPF));
if (!_cdArchive) {
error("Out of memory");
}
_hdArchiveNumFiles = _hdFilePointer->readUint32LE();
if (_hdFilePointer->err()) {
error("Error reading from file \"%s\"", _engine->isDemo() ? "DEMO.HPF" : "HD.hpf");
}
_hdFilePosition += 4;
_hdArchive = (HPF *)malloc(_hdArchiveNumFiles * sizeof(HPF));
if (!_hdArchive) {
error("Out of memory");
}
for (int i = 0; i < _hdArchiveNumFiles; i++) {
_hdFilePointer->read(_hdArchive[i].name, sizeof(_hdArchive[i].name));
_hdArchive[i].offset = _hdFilePointer->readUint32LE();
_hdArchive[i].size = _hdFilePointer->readUint16LE();
_hdArchive[i].currentPos = _hdFilePointer->readUint16LE();
_hdArchive[i].status = _hdFilePointer->readUint16LE();
}
if (_hdFilePointer->err()) {
error("Error reading from file \"%s\"", _engine->isDemo() ? "DEMO.HPF" : "HD.hpf");
}
_hdFilePosition += _hdArchiveNumFiles;
}
void ArchiveManager::shutDownHPFS() {
unlockCD();
if (_hdFilePointer)
_hdFilePointer->close();
if (_hdFilePointer && _hdFilePointer->isOpen()) {
error("Error closing file \"%s\"", "HD cache file");
}
if (_hdArchive) {
free(_hdArchive);
_hdArchive = nullptr;
}
if (_cdArchive) {
free(_cdArchive);
_cdArchive = nullptr;
}
delete _hdFilePointer;
_hdFilePointer = nullptr;
_hdArchiveNumFiles = 0;
_hdFilePosition = 0;
}
void ArchiveManager::unlockCD() {
if (_cdFilePointer)
_cdFilePointer->close();
_cdArchiveNumFiles = 0;
_cdFilePosition = 0;
_cdArchiveIndex = 0;
}
HPF *ArchiveManager::openHPF(const char *filename) {
HPF *result;
Common::String filenameStr(filename);
filenameStr.toUppercase();
if (!_hdFilePointer)
return nullptr;
result = search(filenameStr.c_str(), _hdArchive, _hdArchiveNumFiles);
if (!result) {
if (_cdArchiveNumFiles)
result = search(filenameStr.c_str(), _cdArchive, _cdArchiveNumFiles);
if (!result)
return nullptr;
}
if ((result->status & kHPFFileIsLoaded) != 0)
return nullptr;
result->status |= kHPFFileIsLoaded;
result->currentPos = 0;
return result;
}
void ArchiveManager::readHD(void *dstBuf, int offset, uint32 size) {
if (_hdFilePointer && _hdFilePointer->isOpen()) {
if (offset != _hdFilePosition) {
if (!_hdFilePointer->seek(offset * MEM_PAGE_SIZE, SEEK_SET)) {
error("Error seeking in file \"%s\"", "HD cache file");
}
}
uint32 readSize = _hdFilePointer->read(dstBuf, size * MEM_PAGE_SIZE);
if (readSize != size * MEM_PAGE_SIZE) {
error("Error reading from file \"%s\"", "HD cache file");
}
_hdFilePosition = (offset + size);
}
}
void ArchiveManager::readCD(void *dstBuf, int offset, uint32 size) {
if (_cdFilePointer && _cdFilePointer->isOpen()) {
if (offset != _cdFilePosition) {
if (!_cdFilePointer->seek(offset * MEM_PAGE_SIZE, SEEK_SET)) {
error("Error seeking in file \"%s\"", "CD cache file");
}
}
uint32 readSize = _cdFilePointer->read(dstBuf, size * MEM_PAGE_SIZE);
if (readSize != size * MEM_PAGE_SIZE) {
error("Error reading from file \"%s\"", "CD cache file");
}
_cdFilePosition = (offset + readSize);
}
}
void ArchiveManager::readHPF(HPF *archive, void *dstBuf, uint32 size) {
uint32 effSize;
if ((archive->status & kHPFFileIsLoaded) != 0) {
if (archive->size > archive->currentPos) {
effSize = size;
if (archive->currentPos + size > archive->size)
effSize = archive->size - archive->currentPos;
if ((archive->status & kHPFFileIsOnCD) != 0) {
readHD(dstBuf, (archive->currentPos + archive->offset), effSize);
} else {
readCD(dstBuf, (archive->currentPos + archive->offset), effSize);
}
archive->currentPos += effSize;
}
}
}
void ArchiveManager::seekHPF(HPF *archive, uint32 position) {
if ((archive->status & kHPFFileIsLoaded) != 0 && archive->size > position)
archive->currentPos = position;
}
void ArchiveManager::closeHPF(HPF *archive) {
if ((archive->status & kHPFFileIsLoaded) != 0) {
archive->status &= ~kHPFFileIsLoaded;
}
}
int ArchiveManager::loadBG(const char *filename) {
TBM tbm;
char bgFilename[84];
memset(bgFilename, 0, sizeof(bgFilename));
tbm.x = _engine->getGraphicsManager()->_renderBox1.x;
tbm.y = _engine->getGraphicsManager()->_renderBox1.y;
tbm.width = _engine->getGraphicsManager()->_renderBox1.width;
tbm.height = _engine->getGraphicsManager()->_renderBox1.height;
PixMap *bgSurface = _engine->getGraphicsManager()->_frontBuffer;
if (_engine->getLogicManager()->_doubleClickFlag &&
(_engine->mouseHasLeftClicked() || _engine->mouseHasRightClicked()) &&
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property != kNodeAutoWalk) {
return -1;
}
Common::strcpy_s(bgFilename, filename);
Common::strcat_s(bgFilename, ".bg");
HPF *archive = openHPF(bgFilename);
if (!archive) {
Common::strcpy_s(bgFilename, "DEFAULT.BG");
archive = openHPF(bgFilename);
}
if (archive) {
_engine->getGraphicsManager()->initDecomp(bgSurface, &_engine->getGraphicsManager()->_renderBox1);
bool keepGoing = true;
do {
_engine->getSoundManager()->soundThread();
_engine->getSubtitleManager()->subThread();
readHPF(archive, _engine->getGraphicsManager()->_backgroundCompBuffer, 8);
keepGoing = _engine->getGraphicsManager()->decomp16(_engine->getGraphicsManager()->_backgroundCompBuffer, 0x4000);
keepGoing &= (!_engine->getLogicManager()->_doubleClickFlag ||
(!_engine->mouseHasLeftClicked() && !_engine->mouseHasRightClicked()) ||
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property == kNodeAutoWalk);
} while (keepGoing);
_engine->getGraphicsManager()->modifyPalette(bgSurface, 640 * 480);
closeHPF(archive);
if (_engine->getLogicManager()->_doubleClickFlag &&
(_engine->mouseHasLeftClicked() || _engine->mouseHasRightClicked()) &&
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property != kNodeAutoWalk) {
return -1;
} else {
for (int32 i = _engine->getGraphicsManager()->_renderBox1.y - 1 + _engine->getGraphicsManager()->_renderBox1.height; i >= _engine->getGraphicsManager()->_renderBox1.y; i--) {
memmove(
&bgSurface[640 * i + _engine->getGraphicsManager()->_renderBox1.x],
&bgSurface[_engine->getGraphicsManager()->_renderBox1.width * (i - _engine->getGraphicsManager()->_renderBox1.y)],
2 * _engine->getGraphicsManager()->_renderBox1.width
);
}
if (_engine->getGraphicsManager()->_renderBox1.x) {
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_frontBuffer, 0, 0, _engine->getGraphicsManager()->_renderBox1.x, 480);
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_frontBuffer, 640 - _engine->getGraphicsManager()->_renderBox1.x, 0, _engine->getGraphicsManager()->_renderBox1.x, 480);
}
if (_engine->getGraphicsManager()->_renderBox1.y) {
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_frontBuffer, _engine->getGraphicsManager()->_renderBox1.x, 0, _engine->getGraphicsManager()->_renderBox1.width, _engine->getGraphicsManager()->_renderBox1.y);
_engine->getGraphicsManager()->clear(
_engine->getGraphicsManager()->_frontBuffer,
_engine->getGraphicsManager()->_renderBox1.x,
480 - _engine->getGraphicsManager()->_renderBox1.y,
_engine->getGraphicsManager()->_renderBox1.width,
_engine->getGraphicsManager()->_renderBox1.y
);
}
if (_engine->getLogicManager()->_doubleClickFlag &&
(_engine->mouseHasLeftClicked() || _engine->mouseHasRightClicked()) &&
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property != kNodeAutoWalk) {
return -1;
} else {
_engine->getGraphicsManager()->copy(_engine->getGraphicsManager()->_frontBuffer, _engine->getGraphicsManager()->_backBuffer, 0, 0, 640, 480);
if (tbm.x != _engine->getGraphicsManager()->_renderBox1.x ||
tbm.y != _engine->getGraphicsManager()->_renderBox1.y ||
tbm.width != _engine->getGraphicsManager()->_renderBox1.width ||
tbm.height != _engine->getGraphicsManager()->_renderBox1.height) {
_engine->getGraphicsManager()->_renderBox2.x = tbm.x;
_engine->getGraphicsManager()->_renderBox2.y = tbm.y;
_engine->getGraphicsManager()->_renderBox2.width = tbm.width;
_engine->getGraphicsManager()->_renderBox2.height = tbm.height;
if (_engine->getGraphicsManager()->_renderBox2.x > _engine->getGraphicsManager()->_renderBox1.x)
_engine->getGraphicsManager()->_renderBox2.x = _engine->getGraphicsManager()->_renderBox1.x;
if (_engine->getGraphicsManager()->_renderBox2.y > _engine->getGraphicsManager()->_renderBox1.y)
_engine->getGraphicsManager()->_renderBox2.y = _engine->getGraphicsManager()->_renderBox1.y;
if (_engine->getGraphicsManager()->_renderBox1.height + _engine->getGraphicsManager()->_renderBox1.y > (_engine->getGraphicsManager()->_renderBox2.height + _engine->getGraphicsManager()->_renderBox2.y))
_engine->getGraphicsManager()->_renderBox2.height = _engine->getGraphicsManager()->_renderBox1.height + _engine->getGraphicsManager()->_renderBox1.y - _engine->getGraphicsManager()->_renderBox2.y;
if (_engine->getGraphicsManager()->_renderBox1.x + _engine->getGraphicsManager()->_renderBox1.width > (_engine->getGraphicsManager()->_renderBox2.x + _engine->getGraphicsManager()->_renderBox2.width))
_engine->getGraphicsManager()->_renderBox2.width = _engine->getGraphicsManager()->_renderBox1.x + _engine->getGraphicsManager()->_renderBox1.width - _engine->getGraphicsManager()->_renderBox2.x;
return 1;
}
return 0;
}
}
} else {
memset(_engine->getGraphicsManager()->_backBuffer, 0, (640 * 480 * sizeof(PixMap)));
_engine->getGraphicsManager()->copy(_engine->getGraphicsManager()->_backBuffer, _engine->getGraphicsManager()->_frontBuffer, 0, 0, 640, 480);
_engine->getGraphicsManager()->_renderBox1.x = 0;
_engine->getGraphicsManager()->_renderBox1.y = 0;
_engine->getGraphicsManager()->_renderBox1.width = 640;
_engine->getGraphicsManager()->_renderBox1.height = 480;
_engine->getGraphicsManager()->_renderBox2.x = 0;
_engine->getGraphicsManager()->_renderBox2.y = _engine->getGraphicsManager()->_renderBox1.y;
_engine->getGraphicsManager()->_renderBox2.width = _engine->getGraphicsManager()->_renderBox1.width;
_engine->getGraphicsManager()->_renderBox2.height = _engine->getGraphicsManager()->_renderBox1.height;
return 1;
}
}
Seq *ArchiveManager::loadSeq(const char *filename, uint8 ticksToWaitUntilCycleRestart, int character) {
// Just like for LogicManager::loadTrain(), this function originally used
// pointer fix-ups. This is maybe even worse in this case, since things like
// the compressed data buffer don't have any size specified. This means that
// other than the sequence itself we have to allocate and keep the raw file
// data somewhere, in order to be actually able to deallocate it later...
HPF *archive = openHPF(filename);
if (!archive)
return nullptr;
byte *seqDataRaw = (byte *)_engine->getMemoryManager()->allocMem(MEM_PAGE_SIZE * archive->size, filename, character);
if (!seqDataRaw)
return nullptr;
uint16 i;
byte *seqDataRawCur = seqDataRaw;
for (i = archive->size; i > 8; i -= 8) {
_engine->getSoundManager()->soundThread();
_engine->getSubtitleManager()->subThread();
readHPF(archive, seqDataRawCur, 8);
seqDataRawCur += (MEM_PAGE_SIZE * 8);
}
readHPF(archive, seqDataRawCur, i);
closeHPF(archive);
Seq *seq = new Seq();
// Again, there is no such thing in the original...
seq->rawSeqData = seqDataRaw;
Common::SeekableReadStream *seqDataStream = new Common::MemoryReadStream(seqDataRaw, MEM_PAGE_SIZE * archive->size, DisposeAfterUse::NO);
seq->numFrames = seqDataStream->readUint32LE();
seqDataStream->readUint32LE(); // Empty sprite pointer
seq->sprites = new Sprite[seq->numFrames];
uint32 paletteOffset = 0;
for (int frame = 0; frame < seq->numFrames; frame++) {
uint32 compDataOffset = seqDataStream->readUint32LE();
if (compDataOffset)
seq->sprites[frame].compData = &seqDataRaw[compDataOffset];
uint32 eraseDataOffset = seqDataStream->readUint32LE();
if (eraseDataOffset)
seq->sprites[frame].eraseMask = &seqDataRaw[eraseDataOffset];
paletteOffset = seqDataStream->readUint32LE();
if (paletteOffset)
seq->sprites[frame].colorPalette = (uint16 *)&seqDataRaw[paletteOffset];
seq->sprites[frame].rect.left = seqDataStream->readUint32LE();
seq->sprites[frame].rect.top = seqDataStream->readUint32LE();
seq->sprites[frame].rect.right = seqDataStream->readUint32LE();
seq->sprites[frame].rect.bottom = seqDataStream->readUint32LE();
seq->sprites[frame].rect.width = seqDataStream->readUint32LE();
seq->sprites[frame].rect.height = seqDataStream->readUint32LE();
seq->sprites[frame].hotspotX1 = seqDataStream->readUint16LE();
seq->sprites[frame].hotspotX2 = seqDataStream->readUint16LE();
seq->sprites[frame].hotspotY1 = seqDataStream->readUint16LE();
seq->sprites[frame].hotspotY2 = seqDataStream->readUint16LE();
seq->sprites[frame].compBits = seqDataStream->readByte();
seq->sprites[frame].compType = seqDataStream->readByte();
seq->sprites[frame].copyScreenAndRedrawFlag = seqDataStream->readByte();
seq->sprites[frame].spritesUnk3 = seqDataStream->readByte();
seq->sprites[frame].ticksToWaitUntilCycleRestart = seqDataStream->readByte();
if (seq->sprites[frame].ticksToWaitUntilCycleRestart == 0)
seq->sprites[frame].ticksToWaitUntilCycleRestart = ticksToWaitUntilCycleRestart;
seq->sprites[frame].soundDelay = seqDataStream->readByte();
seq->sprites[frame].soundAction = seqDataStream->readByte();
seq->sprites[frame].flags = seqDataStream->readByte();
seq->sprites[frame].position = seqDataStream->readByte();
seq->sprites[frame].spritesUnk9 = seqDataStream->readByte();
seq->sprites[frame].spritesUnk10 = seqDataStream->readByte();
seq->sprites[frame].spritesUnk11 = seqDataStream->readByte();
seq->sprites[frame].spritesUnk8 = (int)seqDataStream->readUint32LE();
seq->sprites[frame].visibilityDist = seqDataStream->readUint16LE();
seq->sprites[frame].hotspotPriority = seqDataStream->readUint16LE();
seqDataStream->readUint32LE(); // Empty "next" sprite pointer
}
delete seqDataStream;
// Where 68 is the original size of the Sprite struct and 8 is the
// offset from the start of the sequence data to the begining of the
// sprite data...
uint16 *paletteAddr = (uint16 *)&seqDataRaw[8 + 68 * seq->numFrames];
for (int j = 0; j < 184; j++) {
paletteAddr[j] = READ_LE_UINT16(&paletteAddr[j]);
}
_engine->getGraphicsManager()->modifyPalette(paletteAddr, 184);
return seq;
}
void ArchiveManager::loadMice() {
HPF *archive = openHPF("CURSORS.TBM");
if (archive) {
readHPF(archive, _engine->_cursorsMemoryPool, archive->size);
closeHPF(archive);
for (int i = 0; i < 0xC000; i++) {
_engine->getGraphicsManager()->_iconsBitmapData[i] = (PixMap)READ_LE_UINT16(&_engine->getGraphicsManager()->_iconsBitmapData[i]);
}
for (int i = 0; i < 48; i++) {
_engine->getGraphicsManager()->_cursorsDataHeader[i].hotspotX = READ_LE_INT16(&_engine->getGraphicsManager()->_cursorsDataHeader[i].hotspotX);
_engine->getGraphicsManager()->_cursorsDataHeader[i].hotspotY = READ_LE_INT16(&_engine->getGraphicsManager()->_cursorsDataHeader[i].hotspotY);
}
_engine->getGraphicsManager()->modifyPalette(_engine->getGraphicsManager()->_iconsBitmapData, 0xC000);
}
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,118 @@
/* 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/>.
*
*/
#ifndef LASTEXPRESS_ARCHIVE_H
#define LASTEXPRESS_ARCHIVE_H
#include "lastexpress/lastexpress.h"
#include "common/file.h"
namespace LastExpress {
struct Seq;
/*
* HPF Archive Format
*
* * uint32 {4} Number of files
*
* For each file:
* * char {12} Name (zero-terminated)
* * uint32 {4} Offset (expressed in sectors of 2048 bytes)
* * uint16 {2} Size (expressed in sectors of 2048 bytes)
* * uint16 {2} Current position (expressed in sectors of 2048 bytes)
* * uint16 {2} File status flags:
* - Bit 0: "Is on CD"
* - Bit 1: "Is loaded"
*/
typedef struct HPF {
char name[12];
uint32 offset;
uint16 size;
uint16 currentPos;
uint16 status;
// For Gold Edition only
Common::ArchiveMemberPtr archiveRef;
Common::String archiveName;
HPF() {
memset(name, 0, sizeof(name));
offset = 0;
size = 0;
currentPos = 0;
status = 0;
archiveRef = nullptr;
archiveName = "";
};
} HPF;
enum HPFFlags {
kHPFFileIsOnCD = 1 << 0,
kHPFFileIsLoaded = 1 << 1,
};
class ArchiveManager {
public:
ArchiveManager(LastExpressEngine *engine);
virtual ~ArchiveManager();
HPF *search(const char *name, HPF *archive, int archiveSize);
virtual bool lockCD(int32 index);
virtual bool isCDAvailable(int cdNum, char *outPath, int pathSize);
virtual bool lockCache(char *filename);
virtual void initHPFS();
virtual void shutDownHPFS();
void unlockCD();
virtual HPF *openHPF(const char *filename);
void readHD(void *dstBuf, int offset, uint32 size);
void readCD(void *dstBuf, int offset, uint32 size);
virtual void readHPF(HPF *archive, void *dstBuf, uint32 size);
void seekHPF(HPF *archive, uint32 position);
void closeHPF(HPF *archive);
virtual int loadBG(const char *filename);
Seq *loadSeq(const char *filename, uint8 ticksToWaitUntilCycleRestart, int character);
void loadMice();
protected:
LastExpressEngine *_engine = nullptr;
Common::File *_cdFilePointer = nullptr;
int32 _cdFilePosition = 0;
int32 _cdArchiveNumFiles = 0;
Common::File *_hdFilePointer = nullptr;
int32 _hdFilePosition = 0;
int32 _hdArchiveNumFiles = 0;
HPF *_cdArchive = nullptr;
HPF *_hdArchive = nullptr;
int32 _cdArchiveIndex = 0;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_ARCHIVE_H

View File

@@ -0,0 +1,721 @@
#include "cvcrfile.h"
/* 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/>.
*
*/
#include "lastexpress/lastexpress.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/savefile.h"
#include "common/stream.h"
#include "engines/metaengine.h"
namespace LastExpress {
CVCRFile::CVCRFile(LastExpressEngine *engine) {
_engine = engine;
_filename = "";
_virtualFileIsOpen = false;
_virtualFileSize = 0;
_rwStreamSize = 0;
memset(_rleDstBuf, 0, sizeof(_rleDstBuf));
_rleBufferPosition = -1;
_rleByte = 0;
_rleCount = 0;
_rleMode = 0;
_bytesProcessed = 0;
_rleStatus = 0;
_fileMode = 0;
}
CVCRFile::~CVCRFile() {
close();
}
bool CVCRFile::open(const char *filename, int mode) {
_filename = filename;
_rleBufferPosition = -1;
switch (mode) {
case CVCRMODE_WB:
debug(6, "CVCRFile::open(): Opening file \'%s\' on mode 0 (\'wb\')", filename);
break;
case CVCRMODE_RB:
debug(6, "CVCRFile::open(): Opening file \'%s\' on mode 0 (\'rb\')", filename);
break;
case CVCRMODE_RWB:
debug(6, "CVCRFile::open(): Opening file \'%s\' on mode 0 (\'r+b\')", filename);
break;
default:
error("CVCRFile::open(): Invalid file mode");
}
Common::OutSaveFile *outSaveFile = nullptr;
Common::InSaveFile *inSaveFile = nullptr;
if (mode == CVCRMODE_WB) {
outSaveFile = g_engine->getSaveFileManager()->openForSaving(_engine->getTargetName() + "-" + Common::String(_filename), false);
_virtualFileIsOpen = outSaveFile != nullptr;
if (outSaveFile)
_virtualFileSize = (uint32)outSaveFile->size();
} else {
inSaveFile = g_engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(_filename));
_virtualFileIsOpen = inSaveFile != nullptr;
if (inSaveFile)
_virtualFileSize = (uint32)inSaveFile->size();
}
_fileMode = mode;
if (!_virtualFileIsOpen) {
error("Error opening file \"%s\". It probably doesn\'t exist or is write protected.", _filename.c_str());
} else {
// If this file was opened on 'r+b' mode, allocate at least 16kb
uint32 fileSize = ((_fileMode == CVCRMODE_RWB || _fileMode == CVCRMODE_WB) && _virtualFileSize < 16 * 1024) ? 16 * 1024 : _virtualFileSize;
if (inSaveFile && fileSize) {
byte *fileContent = (byte *)malloc(fileSize);
if (!fileContent) {
error("Out of memory");
}
inSaveFile->read(fileContent, fileSize);
_rwStream = new Common::MemorySeekableReadWriteStream(fileContent, fileSize, DisposeAfterUse::YES);
_rwStreamSize = fileSize;
delete inSaveFile;
}
if (outSaveFile && fileSize) {
byte *fileContent = (byte *)malloc(fileSize);
if (!fileContent) {
error("Out of memory");
}
memset(fileContent, 0, fileSize);
_rwStream = new Common::MemorySeekableReadWriteStream(fileContent, fileSize, DisposeAfterUse::YES);
_rwStreamSize = fileSize;
outSaveFile->finalize();
delete outSaveFile;
}
}
return _virtualFileIsOpen;
}
uint32 CVCRFile::read(void *dstBuf, uint32 elementSize, uint32 elementCount, bool forceSimpleReadOnRle, bool checkSize) {
uint32 readAmount;
if ((forceSimpleReadOnRle || !_rleStatus) && _virtualFileIsOpen) {
readAmount = _rwStream->read(dstBuf, elementSize * elementCount);
if (checkSize) {
if (readAmount != elementSize * elementCount) {
error("Error reading from file \"%s\"", _filename.c_str());
}
}
return readAmount / elementSize;
} else {
error("Error reading from file \"%s\"", _filename.c_str());
return 0;
}
}
uint32 CVCRFile::write(void *srcBuf, uint32 elementSize, uint32 elementCount, bool forceSimpleWriteOnRle) {
uint32 wroteAmount;
if ((forceSimpleWriteOnRle || !_rleStatus) && _virtualFileIsOpen) {
// Check if we have to allocate a bigger stream...
uint32 amountToWrite = elementSize * elementCount;
int64 atWhichPos = _rwStream->pos();
uint32 oldSize = _rwStreamSize;
bool haveToRaiseStreamSize = amountToWrite + atWhichPos > _rwStreamSize;
if (haveToRaiseStreamSize) {
while (amountToWrite + atWhichPos > _rwStreamSize) {
_rwStreamSize += 16 * 1024; // Add another 16 kb until we can fit the new data...
}
// Allocate the new and bigger buffer and create a new stream with it...
byte *newBuffer = (byte *)malloc(_rwStreamSize);
if (!newBuffer) {
error("Out of memory");
}
Common::MemorySeekableReadWriteStream *newStream = new Common::MemorySeekableReadWriteStream(newBuffer, _rwStreamSize, DisposeAfterUse::YES);
// Create a temporary buffer in which we will pour the old data...
byte *tmp = (byte *)malloc(oldSize);
if (!tmp) {
error("Out of memory");
}
// Read the old data from the old stream to the temporary buffer...
_rwStream->seek(0, SEEK_SET);
_rwStream->read(tmp, oldSize);
// Delete the old stream...
_rwStream->finalize();
delete _rwStream;
// Update the pointer for _rwStream with the new one...
_rwStream = newStream;
_rwStream->write(tmp, oldSize);
_rwStream->seek(atWhichPos, SEEK_SET);
// Clean-up...
free(tmp);
}
wroteAmount = _rwStream->write(srcBuf, amountToWrite);
if (wroteAmount != amountToWrite) {
error("Error writing to file \"%s\". Your disk is probably full.", _filename.c_str());
}
// Update virtual file size...
_virtualFileSize = _virtualFileSize < (amountToWrite + atWhichPos) ? (amountToWrite + atWhichPos) : _virtualFileSize;
return wroteAmount / elementSize;
} else {
error("Error writing to file \"%s\". Your disk is probably full.", _filename.c_str());
return 0;
}
}
uint32 CVCRFile::readRLE(void *dstBuf, uint32 elementSize, uint32 elementCount) {
uint8 curRleOp;
uint8 *value = (uint8 *)dstBuf;
uint32 remaining = elementSize * elementCount;
if (_rleStatus == 2) {
error("Error reading from file \"%s\"", _filename.c_str());
return 0;
}
_rleStatus = 1;
if (remaining != 0) {
while (true) {
if (_rleMode >= 2) {
if (_rleMode == 2) {
*value = _rleByte;
value++;
_rleCount--;
if (!_rleCount)
_rleMode = 1;
}
if (!--remaining)
return elementCount;
continue;
}
if (_rleBufferPosition == -1 || _rleBufferPosition >= 256) {
read(_rleDstBuf, 1, 256, true, false);
_rleBufferPosition = 0;
}
curRleOp = _rleDstBuf[_rleBufferPosition];
_rleBufferPosition++;
switch (curRleOp) {
case 0xFE:
if (_rleBufferPosition == -1 || _rleBufferPosition >= 256) {
read(_rleDstBuf, 1, 256, true, false);
_rleBufferPosition = 0;
}
curRleOp = _rleDstBuf[_rleBufferPosition];
_rleBufferPosition++;
*value = curRleOp;
value++;
break;
case 0xFF:
if (_rleBufferPosition == -1 || _rleBufferPosition >= 256) {
read(_rleDstBuf, 1, 256, true, false);
_rleBufferPosition = 0;
}
_rleCount = _rleDstBuf[_rleBufferPosition] - 1;
_rleBufferPosition++;
if (_rleBufferPosition == -1 || _rleBufferPosition >= 256) {
read(_rleDstBuf, 1, 256, true, false);
_rleBufferPosition = 0;
}
_rleByte = _rleDstBuf[_rleBufferPosition];
_rleBufferPosition++;
*value = _rleByte;
value++;
_rleMode = 2;
break;
case 0xFB:
_rleCount = 2;
_rleByte = 0;
*value = 0;
value++;
_rleMode = 2;
break;
case 0xFD:
if (_rleBufferPosition == -1 || _rleBufferPosition >= 256) {
read(_rleDstBuf, 1, 256, 1, 0);
_rleBufferPosition = 0;
}
_rleByte = 0;
_rleCount = _rleDstBuf[_rleBufferPosition] - 1;
_rleBufferPosition++;
*value = 0;
value++;
_rleMode = 2;
break;
case 0xFC:
_rleCount = 0xFE;
_rleByte = 0;
*value = 0;
value++;
_rleMode = 2;
break;
default:
*value = curRleOp;
value++;
break;
}
if (!--remaining)
return elementCount;
} // while
}
return elementCount;
}
uint32 CVCRFile::writeRLE(void *srcBuf, uint32 elementSize, uint32 elementCount) {
uint8 *value = (uint8 *)srcBuf;
uint32 remaining = elementSize * elementCount;
if (_rleStatus == 1) {
error("Error writing to file \"%s\". Your disk is probably full.", _filename.c_str());
return 0;
}
for (_rleStatus = 2; remaining; --remaining) {
switch (_rleMode) {
case 0:
_rleMode = 1;
_rleByte = *value;
value++;
break;
case 1:
if (*value == _rleByte) {
_rleMode = 2;
_rleCount = 2;
value++;
} else {
if (_rleBufferPosition == -1)
_rleBufferPosition = 0;
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
if (_rleByte < 0xFB) {
_rleDstBuf[_rleBufferPosition] = _rleByte;
} else {
_rleDstBuf[_rleBufferPosition] = 0xFE;
}
_bytesProcessed++;
_rleBufferPosition++;
if (_rleByte >= 0xFB) {
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
_rleDstBuf[_rleBufferPosition] = _rleByte;
_rleBufferPosition++;
_bytesProcessed++;
}
_rleByte = *value;
value++;
}
break;
case 2:
if (*value != _rleByte) {
if (_rleCount != 3 || _rleByte) {
if (_rleCount == 0xFF) {
if (_rleByte) {
writeToRLEBuffer(0xFF, 1);
writeToRLEBuffer(_rleCount, 1);
writeToRLEBuffer(_rleByte, 1);
_rleMode = 1;
_rleByte = *value;
value++;
continue;
}
writeToRLEBuffer(0xFC, 1);
} else {
if (_rleByte) {
writeToRLEBuffer(0xFF, 1);
writeToRLEBuffer(_rleCount, 1);
writeToRLEBuffer(_rleByte, 1);
_rleMode = 1;
_rleByte = *value;
value++;
continue;
}
writeToRLEBuffer(0xFD, 1);
writeToRLEBuffer(_rleCount, 1);
}
} else {
if (_rleBufferPosition == -1)
_rleBufferPosition = 0;
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
_rleDstBuf[_rleBufferPosition++] = 0xFB;
_bytesProcessed++;
}
_rleMode = 1;
_rleByte = *value;
value++;
continue;
}
if (_rleCount == 0xFF) {
if (!_rleByte) {
writeToRLEBuffer(0xFC, 1);
_rleMode = 1;
_rleByte = *value;
value++;
continue;
}
writeToRLEBuffer(0xFF, 1);
writeToRLEBuffer(_rleCount, 1);
writeToRLEBuffer(_rleByte, 1);
_rleMode = 1;
_rleByte = *value;
value++;
continue;
}
value++;
_rleCount++;
break;
default:
break;
}
}
return elementCount;
}
void CVCRFile::writeToRLEBuffer(uint8 operation, uint8 flag) {
if (_rleBufferPosition == -1)
_rleBufferPosition = 0;
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
if (flag || operation < 0xFB) {
_rleDstBuf[_rleBufferPosition] = operation;
} else {
_rleDstBuf[_rleBufferPosition] = 0xFE;
}
_bytesProcessed++;
_rleBufferPosition++;
if (!flag && operation >= 0xFB) {
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
_rleDstBuf[_rleBufferPosition] = operation;
_rleBufferPosition++;
_bytesProcessed++;
}
}
#define FINISH_FLUSH_OP \
{ \
if (_rleBufferPosition != -1 && _rleBufferPosition) { \
write(_rleDstBuf, _rleBufferPosition, 1, true); \
\
_rleBufferPosition = -1; \
_rleStatus = 0; \
_rleMode = 0; \
result = _bytesProcessed; \
_bytesProcessed = 0; \
return result; \
} else { \
_rleStatus = 0; \
_rleMode = 0; \
result = _bytesProcessed; \
_bytesProcessed = 0; \
return result; \
} \
} \
uint32 CVCRFile::flush() {
uint32 result;
if (_rleStatus == 2) {
if (_rleMode == 1) {
if (_rleBufferPosition == -1)
_rleBufferPosition = 0;
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, 1);
}
if (_rleByte < 0xFB) {
_rleDstBuf[_rleBufferPosition] = _rleByte;
} else {
_rleDstBuf[_rleBufferPosition] = 0xFE;
}
_rleBufferPosition++;
_bytesProcessed++;
if (_rleByte >= 0xFB) {
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
_rleDstBuf[_rleBufferPosition] = _rleByte;
_rleBufferPosition++;
_bytesProcessed++;
}
FINISH_FLUSH_OP;
}
if (_rleMode != 2) {
FINISH_FLUSH_OP;
}
if (_rleCount == 3 && !_rleByte) {
if (_rleBufferPosition == -1)
_rleBufferPosition = 0;
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
_rleDstBuf[_rleBufferPosition] = 0xFB;
_rleBufferPosition++;
_bytesProcessed++;
FINISH_FLUSH_OP;
}
if (_rleCount == 0xFF) {
if (!_rleByte) {
if (_rleBufferPosition == -1)
_rleBufferPosition = 0;
if (_rleBufferPosition == 256) {
_rleBufferPosition = 0;
write(_rleDstBuf, 256, 1, true);
}
_rleDstBuf[_rleBufferPosition] = 0xFC;
_rleBufferPosition++;
_bytesProcessed++;
FINISH_FLUSH_OP;
}
} else if (!_rleByte) {
writeToRLEBuffer(0xFD, 1);
writeToRLEBuffer(_rleCount, 1);
FINISH_FLUSH_OP;
}
writeToRLEBuffer(0xFF, 1);
writeToRLEBuffer(_rleCount, 1);
writeToRLEBuffer(_rleByte, 1);
FINISH_FLUSH_OP;
}
if (_rleStatus == 1) {
_rleStatus = 0;
if (_rleBufferPosition != -1 && _rleBufferPosition != 256) {
seek(_rleBufferPosition - 256, SEEK_CUR);
_rleBufferPosition = -1;
_rleStatus = 0;
_rleMode = 0;
result = _bytesProcessed;
_bytesProcessed = 0;
return result;
}
}
_rleStatus = 0;
_rleMode = 0;
result = _bytesProcessed;
_bytesProcessed = 0;
return result;
}
#undef FINISH_FLUSH_OP
uint32 CVCRFile::seek(int32 offset, int mode) {
int result;
if (_rleStatus) {
error("Error seeking in file \"%s\"", _filename.c_str());
return 0;
} else {
if (_virtualFileIsOpen) {
result = !_rwStream->seek(offset, mode);
if (result) {
error("Error seeking in file \"%s\"", _filename.c_str());
}
return result;
} else {
error("Error seeking in file \"%s\"", _filename.c_str());
return 0;
}
}
}
void CVCRFile::close() {
if (_virtualFileIsOpen) {
Common::OutSaveFile *dumpFile = _engine->getSaveFileManager()->openForSaving(_engine->getTargetName() + "-" + Common::String(_filename), false);
bool dumpFileIsOpen = dumpFile != nullptr;
if (!dumpFileIsOpen) {
error("Error opening file \"%s\". It probably doesn\'t exist or is write protected.", _filename.c_str());
}
_rwStream->seek(0, SEEK_SET);
assert(_virtualFileSize <= _rwStream->size());
byte *dumpFileBuf = (byte *)malloc(_virtualFileSize);
if (!dumpFileBuf) {
error("Out of memory");
}
_rwStream->read(dumpFileBuf, _virtualFileSize);
dumpFile->write(dumpFileBuf, _virtualFileSize);
if (!dumpFile->flush()) {
error("Error closing file \"%s\"", _filename.c_str());
}
// This block is not in the original and is used to ensure portability for VCR::shuffleGames()
if (_fileMode == CVCRMODE_WB || _fileMode == CVCRMODE_RWB) {
Common::String tsFilename = _filename;
tsFilename.chop(4);
tsFilename = _engine->getTargetName() + "-" + tsFilename + ".timestamp";
if (_engine->getSaveFileManager()->exists(tsFilename))
_engine->getSaveFileManager()->removeSavefile(tsFilename);
Common::OutSaveFile *tsFile = _engine->getSaveFileManager()->openForSaving(tsFilename, false);
assert(tsFile);
// Add the seconds to the timestamp, so that we have the same granularity as the original...
TimeDate td;
_engine->_system->getTimeAndDate(td, true);
tsFile->writeSint32LE((int32)td.tm_sec);
// Append the extended save header, which includes the file modification timestamp (date and hours/minutes)...
_engine->getMetaEngine()->appendExtendedSave(tsFile, _engine->getTotalPlayTime(), _filename, false);
tsFile->finalize();
delete tsFile;
}
dumpFile->finalize();
delete dumpFile;
free(dumpFileBuf);
_rwStream->finalize();
delete _rwStream;
_rwStream = nullptr;
_rwStreamSize = 0;
_virtualFileSize = 0;
_virtualFileIsOpen = false;
}
}
int32 CVCRFile::tell() {
if (_rleStatus || !_virtualFileIsOpen) {
error("Error telling in file \"%s\"", _filename.c_str());
return 0;
} else {
return (int32)_rwStream->pos();
}
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,98 @@
/* 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/>.
*
*/
#ifndef LASTEXPRESS_CVCRFILE_H
#define LASTEXPRESS_CVCRFILE_H
#include "lastexpress/lastexpress.h"
#include "lastexpress/shared.h"
namespace Common {
class MemorySeekableReadWriteStream;
class SeekableReadStream;
}
namespace LastExpress {
#define CVCRMODE_WB 0
#define CVCRMODE_RB 1
#define CVCRMODE_RWB 2
struct SVCRFileHeader {
int32 magicNumber;
int32 numVCRGames;
int32 nextWritePos;
int32 realWritePos;
int32 lastIsTemporary;
int32 brightness;
int32 volume;
int32 saveVersion;
};
struct SVCRSavePointHeader {
int32 magicNumber;
int32 type;
int32 time;
int32 size;
int32 partNumber;
int32 latestGameEvent;
int32 emptyField1;
int32 emptyField2;
};
class CVCRFile {
public:
CVCRFile(LastExpressEngine *engine);
~CVCRFile();
bool open(const char *filename, int mode);
uint32 read(void *dstBuf, uint32 elementSize, uint32 elementCount, bool forceSimpleReadOnRle, bool checkSize);
uint32 write(void *srcBuf, uint32 elementSize, uint32 elementCount, bool forceSimpleReadOnRle);
uint32 readRLE(void *dstBuf, uint32 elementSize, uint32 elementCount);
uint32 writeRLE(void *srcBuf, uint32 elementSize, uint32 elementCount);
void writeToRLEBuffer(uint8 operation, uint8 flag);
uint32 flush();
uint32 seek(int32 offset, int mode);
void close();
int32 tell();
bool fileIsOpen() { return _virtualFileIsOpen; }
private:
LastExpressEngine *_engine = nullptr;
Common::MemorySeekableReadWriteStream *_rwStream = nullptr;
Common::String _filename = "";
bool _virtualFileIsOpen = false;
uint32 _virtualFileSize = 0;
uint32 _rwStreamSize = 0;
uint8 _rleDstBuf[256];
int32 _rleBufferPosition = -1;
uint8 _rleByte = 0;
uint8 _rleCount = 0;
int32 _rleMode = 0;
int32 _bytesProcessed = 0;
int32 _rleStatus = 0;
int32 _fileMode = 0;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_CVCRFILE_H

View File

@@ -0,0 +1,299 @@
/* 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/>.
*
*/
#include "lastexpress/lastexpress.h"
#include "lastexpress/data/gold_archive.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/compression/unzip.h"
#include "image/jpeg.h"
namespace LastExpress {
GoldArchiveManager::GoldArchiveManager(LastExpressEngine *engine) : ArchiveManager(engine) {
}
GoldArchiveManager::~GoldArchiveManager() {
}
bool GoldArchiveManager::lockCD(int32 index) {
return true;
}
bool GoldArchiveManager::isCDAvailable(int cdNum, char *outPath, int pathSize) {
return true;
}
bool GoldArchiveManager::lockCache(char *filename) {
return true;
}
void GoldArchiveManager::initHPFS() {
// This will act as a cache of files
_hdArchiveNumFiles = 6000;
_hdArchive = new HPF[_hdArchiveNumFiles];
Common::StringArray resCategories = {
"BG", "DATA", "LNK", "NIS",
"SBE", "SEQ", "SND", "TGA"
};
for (uint i = 0; i < resCategories.size(); i++) {
Common::Archive *tmpArch = Common::makeZipArchive(Common::Path(Common::String::format("%s.zip", resCategories[i].c_str())), false);
if (tmpArch) {
SearchMan.add(resCategories[i], tmpArch);
} else {
warning("GoldArchiveManager::initHPFS(): %s.zip could't be loaded, the game might not function properly", resCategories[i].c_str());
}
}
}
void GoldArchiveManager::shutDownHPFS() {
delete[] _hdArchive;
}
HPF *GoldArchiveManager::openHPF(const char *filename) {
HPF *result = nullptr;
for (int32 i = 0; i < _hdArchiveNumFiles; i++) {
if ((_hdArchive[i].status & kHPFFileIsLoaded) == 0) {
result = &_hdArchive[i];
break;
}
}
if (!result) {
error("GoldArchiveManager::openHPF(): Couldn't allocate cache for file %s", filename);
}
Common::String filenameStr(filename);
filenameStr.toUppercase();
Common::ArchiveMemberPtr archiveItem = SearchMan.getMember(Common::Path(filenameStr));
if (!archiveItem.get()) {
archiveItem = SearchMan.getMember(Common::Path(Common::Path(Common::String::format("COMMON/%s", filenameStr.c_str()))));
}
if (!archiveItem.get()) {
archiveItem = SearchMan.getMember(Common::Path(Common::Path(Common::String::format("%s/%s", _languagePrefix.c_str(), filenameStr.c_str()))));
}
if (!archiveItem.get()) {
for (int i = 1; i <= 3; i++) {
archiveItem = SearchMan.getMember(Common::Path(Common::Path(Common::String::format("%s/CD%d/%s", _languagePrefix.c_str(), i, filenameStr.c_str()))));
if (archiveItem.get())
break;
archiveItem = SearchMan.getMember(Common::Path(Common::Path(Common::String::format("CD%d/%s", i, filenameStr.c_str()))));
if (archiveItem.get())
break;
}
}
if (!archiveItem.get()) {
warning("GoldArchiveManager::openHPF(): Can't find file %s", filename);
return nullptr;
}
Common::strcpy_s(result->name, filename);
int64 archiveSize = archiveItem.get()->createReadStream()->size();
result->size = (uint16)((archiveSize / MEM_PAGE_SIZE) + ((archiveSize % MEM_PAGE_SIZE) > 0 ? 1 : 0));
result->status |= kHPFFileIsLoaded;
result->currentPos = 0;
result->archiveName = archiveItem.get()->getPathInArchive().toString();
result->archiveRef = archiveItem;
return result;
}
void GoldArchiveManager::readHPF(HPF *archive, void *dstBuf, uint32 size) {
if (!archive)
return;
uint32 effSize;
if ((archive->status & kHPFFileIsLoaded) != 0) {
if (archive->size > archive->currentPos) {
effSize = size;
if (archive->currentPos + size > archive->size)
effSize = archive->size - archive->currentPos;
Common::ArchiveMember *archivePtr = archive->archiveRef.get();
assert(archivePtr);
Common::SeekableReadStream *readStream = archivePtr->createReadStream();
readStream->seek((archive->currentPos + archive->offset) * MEM_PAGE_SIZE, SEEK_SET);
readStream->read(dstBuf, effSize * MEM_PAGE_SIZE);
delete readStream;
archive->currentPos += effSize;
}
}
}
int GoldArchiveManager::loadBG(const char *filename) {
TBM tbm;
char bgFilename[84];
memset(bgFilename, 0, sizeof(bgFilename));
tbm.x = _engine->getGraphicsManager()->_renderBox1.x;
tbm.y = _engine->getGraphicsManager()->_renderBox1.y;
tbm.width = _engine->getGraphicsManager()->_renderBox1.width;
tbm.height = _engine->getGraphicsManager()->_renderBox1.height;
PixMap *bgSurface = _engine->getGraphicsManager()->_frontBuffer;
if (_engine->getLogicManager()->_doubleClickFlag &&
(_engine->mouseHasLeftClicked() || _engine->mouseHasRightClicked()) &&
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property != kNodeAutoWalk) {
return -1;
}
Common::strcpy_s(bgFilename, filename);
Common::strcat_s(bgFilename, ".bg");
HPF *archive = openHPF(bgFilename);
if (!archive) {
Common::strcpy_s(bgFilename, "DEFAULT.BG");
archive = openHPF(bgFilename);
}
if (archive) {
// The background format in the Gold Edition is basically JPEG but with an header on top, containing:
// - X coordinate
// - Y coordinate
// - Width
// - Height
//
// All these fields are 32-bit LE.
Image::JPEGDecoder *dec = new Image::JPEGDecoder();
byte *backgroundCompBuffer = (byte *)malloc(archive->size * MEM_PAGE_SIZE);
assert(backgroundCompBuffer);
readHPF(archive, backgroundCompBuffer, archive->size);
Common::SeekableReadStream *seqDataStream = new Common::MemoryReadStream(backgroundCompBuffer, MEM_PAGE_SIZE * archive->size, DisposeAfterUse::YES);
_engine->getGraphicsManager()->_renderBox1.x = seqDataStream->readUint32LE();
_engine->getGraphicsManager()->_renderBox1.y = seqDataStream->readUint32LE();
_engine->getGraphicsManager()->_renderBox1.width = seqDataStream->readUint32LE();
_engine->getGraphicsManager()->_renderBox1.height = seqDataStream->readUint32LE();
dec->setOutputPixelFormat(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
const Graphics::Surface *decodedSurf = dec->decodeFrame(*seqDataStream);
memcpy(bgSurface, decodedSurf->getPixels(), _engine->getGraphicsManager()->_renderBox1.width * _engine->getGraphicsManager()->_renderBox1.height * sizeof(PixMap));
_engine->getGraphicsManager()->modifyPalette(bgSurface, 640 * 480);
closeHPF(archive);
delete dec;
if (_engine->getLogicManager()->_doubleClickFlag &&
(_engine->mouseHasLeftClicked() || _engine->mouseHasRightClicked()) &&
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property != kNodeAutoWalk) {
return -1;
} else {
for (int32 i = _engine->getGraphicsManager()->_renderBox1.y - 1 + _engine->getGraphicsManager()->_renderBox1.height; i >= _engine->getGraphicsManager()->_renderBox1.y; i--) {
memmove(
&bgSurface[640 * i + _engine->getGraphicsManager()->_renderBox1.x],
&bgSurface[_engine->getGraphicsManager()->_renderBox1.width * (i - _engine->getGraphicsManager()->_renderBox1.y)],
2 * _engine->getGraphicsManager()->_renderBox1.width
);
}
if (_engine->getGraphicsManager()->_renderBox1.x) {
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_frontBuffer, 0, 0, _engine->getGraphicsManager()->_renderBox1.x, 480);
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_frontBuffer, 640 - _engine->getGraphicsManager()->_renderBox1.x, 0, _engine->getGraphicsManager()->_renderBox1.x, 480);
}
if (_engine->getGraphicsManager()->_renderBox1.y) {
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_frontBuffer, _engine->getGraphicsManager()->_renderBox1.x, 0, _engine->getGraphicsManager()->_renderBox1.width, _engine->getGraphicsManager()->_renderBox1.y);
_engine->getGraphicsManager()->clear(
_engine->getGraphicsManager()->_frontBuffer,
_engine->getGraphicsManager()->_renderBox1.x,
480 - _engine->getGraphicsManager()->_renderBox1.y,
_engine->getGraphicsManager()->_renderBox1.width,
_engine->getGraphicsManager()->_renderBox1.y
);
}
if (_engine->getLogicManager()->_doubleClickFlag &&
(_engine->mouseHasLeftClicked() || _engine->mouseHasRightClicked()) &&
_engine->getLogicManager()->_trainData[_engine->getLogicManager()->_activeNode].property != kNodeAutoWalk) {
return -1;
} else {
_engine->getGraphicsManager()->copy(_engine->getGraphicsManager()->_frontBuffer, _engine->getGraphicsManager()->_backBuffer, 0, 0, 640, 480);
if (tbm.x != _engine->getGraphicsManager()->_renderBox1.x ||
tbm.y != _engine->getGraphicsManager()->_renderBox1.y ||
tbm.width != _engine->getGraphicsManager()->_renderBox1.width ||
tbm.height != _engine->getGraphicsManager()->_renderBox1.height) {
_engine->getGraphicsManager()->_renderBox2.x = tbm.x;
_engine->getGraphicsManager()->_renderBox2.y = tbm.y;
_engine->getGraphicsManager()->_renderBox2.width = tbm.width;
_engine->getGraphicsManager()->_renderBox2.height = tbm.height;
if (_engine->getGraphicsManager()->_renderBox2.x > _engine->getGraphicsManager()->_renderBox1.x)
_engine->getGraphicsManager()->_renderBox2.x = _engine->getGraphicsManager()->_renderBox1.x;
if (_engine->getGraphicsManager()->_renderBox2.y > _engine->getGraphicsManager()->_renderBox1.y)
_engine->getGraphicsManager()->_renderBox2.y = _engine->getGraphicsManager()->_renderBox1.y;
if (_engine->getGraphicsManager()->_renderBox1.height + _engine->getGraphicsManager()->_renderBox1.y > (_engine->getGraphicsManager()->_renderBox2.height + _engine->getGraphicsManager()->_renderBox2.y))
_engine->getGraphicsManager()->_renderBox2.height = _engine->getGraphicsManager()->_renderBox1.height + _engine->getGraphicsManager()->_renderBox1.y - _engine->getGraphicsManager()->_renderBox2.y;
if (_engine->getGraphicsManager()->_renderBox1.x + _engine->getGraphicsManager()->_renderBox1.width > (_engine->getGraphicsManager()->_renderBox2.x + _engine->getGraphicsManager()->_renderBox2.width))
_engine->getGraphicsManager()->_renderBox2.width = _engine->getGraphicsManager()->_renderBox1.x + _engine->getGraphicsManager()->_renderBox1.width - _engine->getGraphicsManager()->_renderBox2.x;
return 1;
}
return 0;
}
}
} else {
memset(_engine->getGraphicsManager()->_backBuffer, 0, (640 * 480 * sizeof(PixMap)));
_engine->getGraphicsManager()->copy(_engine->getGraphicsManager()->_backBuffer, _engine->getGraphicsManager()->_frontBuffer, 0, 0, 640, 480);
_engine->getGraphicsManager()->_renderBox1.x = 0;
_engine->getGraphicsManager()->_renderBox1.y = 0;
_engine->getGraphicsManager()->_renderBox1.width = 640;
_engine->getGraphicsManager()->_renderBox1.height = 480;
_engine->getGraphicsManager()->_renderBox2.x = 0;
_engine->getGraphicsManager()->_renderBox2.y = _engine->getGraphicsManager()->_renderBox1.y;
_engine->getGraphicsManager()->_renderBox2.width = _engine->getGraphicsManager()->_renderBox1.width;
_engine->getGraphicsManager()->_renderBox2.height = _engine->getGraphicsManager()->_renderBox1.height;
return 1;
}
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,59 @@
/* 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/>.
*
*/
#ifndef LASTEXPRESS_GOLD_ARCHIVE_H
#define LASTEXPRESS_GOLD_ARCHIVE_H
#include "lastexpress/data/archive.h"
#include "common/file.h"
namespace LastExpress {
class LastExpressEngine;
class ArchiveManager;
struct Seq;
struct HPF;
class GoldArchiveManager : public ArchiveManager {
public:
GoldArchiveManager(LastExpressEngine *engine);
~GoldArchiveManager();
bool lockCD(int32 index) override;
bool isCDAvailable(int cdNum, char *outPath, int pathSize) override;
bool lockCache(char *filename) override;
void initHPFS() override;
void shutDownHPFS() override;
HPF *openHPF(const char *filename) override;
void readHPF(HPF *archive, void *dstBuf, uint32 size) override;
int loadBG(const char *filename) override;
protected:
Common::String _languagePrefix = "EN";
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_GOLD_ARCHIVE_H

View File

@@ -0,0 +1,287 @@
/* 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/>.
*
*/
#include "lastexpress/lastexpress.h"
#include "sprites.h"
namespace LastExpress {
SpriteManager::SpriteManager(LastExpressEngine *engine) {
_engine = engine;
}
void SpriteManager::drawCycle() {
if (_drawSequencesFlag) {
Extent extent = Extent(0, 480, 0, 0, 0, 0);
Sprite *queue = _frameQueue;
int oldLeft = 640;
int oldRight = 0;
if (_coordinatesAreSet) {
if (_eraseRect.right >= _eraseRect.left &&
_eraseRect.right < 640 &&
_eraseRect.top <= _eraseRect.bottom &&
_eraseRect.bottom < 480) {
memcpy(&extent, &_eraseRect, sizeof(extent));
oldRight = extent.right;
oldLeft = extent.left;
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->copy(
_engine->getGraphicsManager()->_backBuffer,
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
_eraseRect.left,
_eraseRect.top,
_eraseRect.right - _eraseRect.left + 1,
_eraseRect.bottom - _eraseRect.top + 1);
_engine->getGraphicsManager()->unlockSurface();
}
}
resetEraseQueue();
}
for (; queue; queue = queue->nextSprite) {
if (queue->copyScreenAndRedrawFlag == 1)
_engine->getGraphicsManager()->eraseSprite(queue->eraseMask);
}
for (Sprite *i = _frameQueue; i; i = i->nextSprite) {
if (i->rect.left < oldLeft)
oldLeft = i->rect.left;
if (i->rect.top < extent.top)
extent.top = i->rect.top;
if (i->rect.right > oldRight)
oldRight = i->rect.right;
if (i->rect.bottom > extent.bottom)
extent.bottom = i->rect.bottom;
if (_engine->getGraphicsManager()->acquireSurface()) {
if (i->compType) {
if (i->compType == 1) {
switch (i->compBits) {
case 3:
_engine->getGraphicsManager()->bitBltWax8(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 4:
_engine->getGraphicsManager()->bitBltWax16(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 5:
_engine->getGraphicsManager()->bitBltWax32(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 7:
_engine->getGraphicsManager()->bitBltWax128(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
default:
break;
}
} else if (i->compType == 2) {
switch (i->compBits) {
case 3:
_engine->getGraphicsManager()->bitBltWane8(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 4:
_engine->getGraphicsManager()->bitBltWane16(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 5:
_engine->getGraphicsManager()->bitBltWane32(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 7:
_engine->getGraphicsManager()->bitBltWane128(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
default:
break;
}
}
} else {
switch (i->compBits) {
case 3:
_engine->getGraphicsManager()->bitBltSprite8(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 4:
_engine->getGraphicsManager()->bitBltSprite16(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 5:
_engine->getGraphicsManager()->bitBltSprite32(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
case 7:
_engine->getGraphicsManager()->bitBltSprite128(i, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
break;
default:
break;
}
}
_engine->getGraphicsManager()->unlockSurface();
}
}
if (oldLeft != 640)
_engine->getGraphicsManager()->burstBox(oldLeft, extent.top, oldRight - oldLeft + 1, extent.bottom - extent.top + 1);
_drawSequencesFlag = false;
}
}
void SpriteManager::drawCycleSimple(PixMap *pixels) {
for (Sprite *i = _frameQueue; i; i = i->nextSprite) {
if (i->compType != 2 && i->compType != 3) {
switch (i->compBits) {
case 3:
_engine->getGraphicsManager()->bitBltSprite8(i, pixels);
break;
case 4:
_engine->getGraphicsManager()->bitBltSprite16(i, pixels);
break;
case 5:
_engine->getGraphicsManager()->bitBltSprite32(i, pixels);
break;
case 7:
_engine->getGraphicsManager()->bitBltSprite128(i, pixels);
break;
default:
continue;
}
}
}
}
void SpriteManager::queueErase(Sprite *sprite) {
if (sprite && sprite->compType != 3) {
_coordinatesAreSet = true;
if (_eraseRect.left > sprite->rect.left)
_eraseRect.left = sprite->rect.left;
if (_eraseRect.top > sprite->rect.top)
_eraseRect.top = sprite->rect.top;
if (_eraseRect.right < sprite->rect.right)
_eraseRect.right = sprite->rect.right;
if (_eraseRect.bottom < sprite->rect.bottom)
_eraseRect.bottom = sprite->rect.bottom;
}
}
void SpriteManager::resetEraseQueue() {
_eraseRect.left = 640;
_eraseRect.top = 480;
_coordinatesAreSet = false;
_eraseRect.right = 0;
_eraseRect.bottom = 0;
}
void SpriteManager::killSpriteQueue() {
_drawSequencesFlag = true;
_frameQueue = nullptr;
}
void SpriteManager::touchSpriteQueue() {
_drawSequencesFlag = true;
}
void SpriteManager::drawSprite(Sprite *sprite) {
if (sprite) {
Sprite *queue = _frameQueue;
while (queue) {
if (queue == sprite)
return;
queue = queue->nextSprite;
}
_drawSequencesFlag = true;
if (_frameQueue) {
bool insertedInQueue = false;
if (sprite->hotspotPriority <= _frameQueue->hotspotPriority) {
queue = _frameQueue;
for (Sprite *i = _frameQueue->nextSprite; !insertedInQueue && i; i = i->nextSprite) {
if (sprite->hotspotPriority > i->hotspotPriority) {
queue->nextSprite = sprite;
sprite->nextSprite = i;
insertedInQueue = true;
}
queue = i;
}
if (!insertedInQueue) {
queue->nextSprite = sprite;
sprite->nextSprite = nullptr;
}
} else {
sprite->nextSprite = _frameQueue;
_frameQueue = sprite;
}
} else {
_frameQueue = sprite;
sprite->nextSprite = nullptr;
}
}
}
void SpriteManager::removeSprite(Sprite *sprite) {
Sprite *queue = _frameQueue;
Sprite *spriteToRemove = nullptr;
if (sprite) {
while (queue != sprite && queue) {
spriteToRemove = queue;
queue = queue->nextSprite;
}
if (queue) {
if (spriteToRemove) {
spriteToRemove->nextSprite = queue->nextSprite;
} else {
_frameQueue = queue->nextSprite;
}
_drawSequencesFlag = true;
}
}
}
void SpriteManager::destroySprite(Sprite **sprites, bool redrawFlag) {
if (sprites && *sprites) {
queueErase(*sprites);
removeSprite(*sprites);
if (redrawFlag)
drawCycle();
*sprites = nullptr;
}
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,89 @@
/* 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/>.
*
*/
#ifndef LASTEXPRESS_SPRITES_H
#define LASTEXPRESS_SPRITES_H
#include "lastexpress/lastexpress.h"
namespace LastExpress {
class LastExpressEngine;
typedef struct Extent {
int left;
int top;
int right;
int bottom;
int width;
int height;
Extent() {
left = 0;
top = 0;
right = 0;
bottom = 0;
width = 0;
height = 0;
}
Extent(int newLeft, int newTop, int newRight, int newBottom, int newWidth, int newHeight) {
left = newLeft;
top = newTop;
right = newRight;
bottom = newBottom;
width = newWidth;
height = newHeight;
}
} Extent;
struct Sprite;
typedef uint16 PixMap;
class SpriteManager {
public:
SpriteManager(LastExpressEngine *engine);
~SpriteManager() {}
void drawCycle();
void drawCycleSimple(PixMap *pixels);
void queueErase(Sprite *sprite);
void resetEraseQueue();
void killSpriteQueue();
void touchSpriteQueue();
void drawSprite(Sprite *sprite);
void removeSprite(Sprite *sprite);
void destroySprite(Sprite **sprites, bool redrawFlag);
private:
LastExpressEngine *_engine = nullptr;
bool _drawSequencesFlag = false;
Sprite *_frameQueue = nullptr;
bool _coordinatesAreSet = false;
Extent _eraseRect = Extent(640, 480, 0, 0, 0, 0);
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_SPRITES_H