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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
/* 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/data/archive.h"
#include "lastexpress/data/gold_archive.h"
#include "lastexpress/game/beetle.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/lastexpress.h"
namespace LastExpress {
CBeetle::CBeetle(LastExpressEngine *engine) {
_engine = engine;
// Walk sequences
_sequences[0] = _engine->getArchiveManager()->loadSeq("BW000.seq", 15, 0);
_sequences[3] = _engine->getArchiveManager()->loadSeq("BW045.seq", 15, 0);
_sequences[6] = _engine->getArchiveManager()->loadSeq("BW090.seq", 15, 0);
_sequences[9] = _engine->getArchiveManager()->loadSeq("BW135.seq", 15, 0);
_sequences[12] = _engine->getArchiveManager()->loadSeq("BW180.seq", 15, 0);
_sequences[15] = _engine->getArchiveManager()->loadSeq("BW225.seq", 15, 0);
_sequences[18] = _engine->getArchiveManager()->loadSeq("BW270.seq", 15, 0);
_sequences[21] = _engine->getArchiveManager()->loadSeq("BW315.seq", 15, 0);
// Angle turn sequences
_sequences[1] = _engine->getArchiveManager()->loadSeq("BT000045.seq", 15, 0);
_sequences[4] = _engine->getArchiveManager()->loadSeq("BT045090.seq", 15, 0);
_sequences[7] = _engine->getArchiveManager()->loadSeq("BT090135.seq", 15, 0);
_sequences[10] = _engine->getArchiveManager()->loadSeq("BT135180.seq", 15, 0);
_sequences[13] = _engine->getArchiveManager()->loadSeq("BT180225.seq", 15, 0);
_sequences[16] = _engine->getArchiveManager()->loadSeq("BT225270.seq", 15, 0);
_sequences[19] = _engine->getArchiveManager()->loadSeq("BT270315.seq", 15, 0);
_sequences[22] = _engine->getArchiveManager()->loadSeq("BT315000.seq", 15, 0);
// Inverse angle turn sequences
_sequences[2] = _engine->getArchiveManager()->loadSeq("BT045000.seq", 15, 0);
_sequences[5] = _engine->getArchiveManager()->loadSeq("BT090045.seq", 15, 0);
_sequences[8] = _engine->getArchiveManager()->loadSeq("BT135090.seq", 15, 0);
_sequences[11] = _engine->getArchiveManager()->loadSeq("BT180135.seq", 15, 0);
_sequences[14] = _engine->getArchiveManager()->loadSeq("BT225180.seq", 15, 0);
_sequences[17] = _engine->getArchiveManager()->loadSeq("BT270225.seq", 15, 0);
_sequences[20] = _engine->getArchiveManager()->loadSeq("BT315270.seq", 15, 0);
_sequences[23] = _engine->getArchiveManager()->loadSeq("BT000315.seq", 15, 0);
// Other sequences
_sequences[24] = _engine->getArchiveManager()->loadSeq("BA135.seq", 15, 0);
_sequences[25] = _engine->getArchiveManager()->loadSeq("BL045.seq", 15, 0);
_sequences[26] = _engine->getArchiveManager()->loadSeq("BL000.seq", 15, 0);
_sequences[27] = _engine->getArchiveManager()->loadSeq("BL315.seq", 15, 0);
_sequences[28] = _engine->getArchiveManager()->loadSeq("BL180.seq", 15, 0);
_sequences[29] = nullptr;
_loaded = true;
for (int i = 0; i < 29; i++) {
if (!_sequences[i]) {
_loaded = false;
break;
}
}
_fleeSpeed = 10;
_coordOffset = 5;
_coords.y = 178;
_currentSequence = nullptr;
_currentDirectionIndex = 0;
_frame = nullptr;
_mouseCooldown = 0;
_directions[0] = 29;
_spawnCounter = 0;
}
CBeetle::~CBeetle() {
if (_currentSequence)
_engine->getSpriteManager()->removeSprite(&_currentSequence->sprites[_currentFrame]);
for (int i = 0; i < ARRAYSIZE(_sequences); i++) {
if (_sequences[i]) {
_engine->getMemoryManager()->freeMem(_sequences[i]->rawSeqData);
delete _sequences[i];
_sequences[i] = nullptr;
}
}
_currentSequence = nullptr;
}
void CBeetle::tick() {
if (!_loaded)
return;
checkMouse();
if (_mouseCooldown)
_mouseCooldown--;
if (!_currentSequence || _directions[_currentDirectionIndex] == 29) {
if (_engine->getLogicManager()->_items[kItemBeetle].floating != 3 ||
((_spawnCounter || rnd(10)) && (_spawnCounter >= 3 || rnd(30)) && rnd(100))) {
return;
}
_spawnCounter++;
if (_spawnCounter > 3)
_spawnCounter = 0;
_engine->_beetle->setDirection(24);
_coords.x = rnd(250) + 190;
_coordOffset = rnd(5) + 5;
if (_fleeSpeed > 1)
_fleeSpeed--;
}
if (_frame) {
_engine->getSpriteManager()->queueErase(_frame);
_engine->getSpriteManager()->removeSprite(_frame);
}
int curDir = _directions[_currentDirectionIndex];
if (curDir == 0 || curDir == 3 || curDir == 6 || curDir == 9 || curDir == 12 || curDir == 15 || curDir == 18 || curDir == 21 || curDir == 24 || curDir == 27 || curDir == 26 || curDir == 25 || curDir == 28) {
_currentFrame++;
} else {
_currentFrame += 10;
}
bool terminate = false;
assert(_currentSequence);
if (_currentSequence->numFrames <= _currentFrame) {
curDir = _directions[_currentDirectionIndex];
if (curDir != 0 && curDir != 3 && curDir != 6 && curDir != 9 && curDir != 12 && curDir != 15 && curDir != 18 && curDir != 21) {
_currentDirectionIndex++;
_currentSequence = _sequences[_directions[_currentDirectionIndex]];
}
_currentFrame = 0;
if (_directions[_currentDirectionIndex] == 29) {
_frame = nullptr;
_currentSequence = nullptr;
terminate = true;
}
}
if (terminate) {
return;
}
curDir = _directions[_currentDirectionIndex];
if (!curDir || curDir == 3 || curDir == 6 || curDir == 9 || curDir == 12 || curDir == 15 || curDir == 18 || curDir == 21) {
switch (curDir) {
case 0:
_coords.y -= _coordOffset;
break;
case 3:
_coords.x += _coordOffset;
_coords.y -= _coordOffset;
break;
case 6:
_coords.x += _coordOffset;
break;
case 9:
_coords.x += _coordOffset;
_coords.y += _coordOffset;
break;
case 12:
_coords.y += _coordOffset;
break;
case 15:
_coords.x -= _coordOffset;
_coords.y += _coordOffset;
break;
case 18:
_coords.x -= _coordOffset;
break;
case 21:
_coords.x -= _coordOffset;
_coords.y -= _coordOffset;
break;
default:
break;
}
}
uint randNum = rnd(100);
if (_coords.x > 465) {
if (randNum >= 30) {
if (randNum >= 70)
setDirection(15);
else
setDirection(18);
} else {
setDirection(21);
}
}
if (_coords.x < 165) {
if (randNum >= 30) {
if (randNum >= 70)
setDirection(9);
else
setDirection(6);
} else {
setDirection(3);
}
}
if (_coords.y < 178) {
curDir = _directions[_currentDirectionIndex];
if (curDir) {
if (curDir == 3) {
setDirection(25);
} else if (curDir == 21) {
setDirection(27);
}
} else {
setDirection(26);
}
}
if (_coords.y > 354) {
curDir = _directions[_currentDirectionIndex];
if (curDir == 9 || curDir == 12 || curDir == 15)
setDirection(28);
}
curDir = _directions[_currentDirectionIndex];
if (curDir == 24 || curDir == 27 || curDir == 26 || curDir == 25 || curDir == 28)
_coords.y = -_coords.y;
_engine->positionSprite(&_currentSequence->sprites[_currentFrame], _coords);
curDir = _directions[_currentDirectionIndex];
if (curDir == 24 || curDir == 27 || curDir == 26 || curDir == 25 || curDir == 28)
_coords.y = -_coords.y;
_engine->getSpriteManager()->drawSprite(&_currentSequence->sprites[_currentFrame]);
_frame = &_currentSequence->sprites[_currentFrame];
}
void CBeetle::checkMouse() {
int16 cursorX;
int16 cursorY;
int curDir;
int scaledDiffY;
int16 diffX;
int16 diffY;
cursorX = _engine->_cursorX;
cursorY = _engine->_cursorY;
curDir = _directions[_currentDirectionIndex];
if (curDir != 29 && curDir != 27 && curDir != 26 && curDir != 25 && curDir != 28 && curDir != 24 &&
!_mouseCooldown && ABS<int16>(cursorX - _coords.x) <= 35) {
if (ABS<int16>(cursorY - _coords.y) <= 35) {
diffX = cursorX - _coords.x;
diffY = _coords.y - cursorY;
if (diffX < 0) {
if (diffY <= 0) {
scaledDiffY = 100 * diffY;
if (scaledDiffY - 41 * diffX <= 0) {
if (scaledDiffY - 241 * diffX <= 0) {
setDirection(0);
} else {
setDirection(3);
}
} else {
setDirection(6);
}
if (_coordOffset >= 15) {
_mouseCooldown = 10;
return;
}
} else {
scaledDiffY = 100 * diffY;
if (scaledDiffY + 241 * diffX <= 0) {
if (scaledDiffY + 41 * diffX <= 0) {
setDirection(6);
} else {
setDirection(9);
}
} else {
setDirection(12);
}
if (_coordOffset >= 15) {
_mouseCooldown = 10;
return;
}
}
} else if (diffY <= 0) {
scaledDiffY = 100 * diffY;
if (scaledDiffY + 41 * diffX <= 0) {
if (scaledDiffY + 241 * diffX <= 0) {
setDirection(0);
} else {
setDirection(21);
}
} else {
setDirection(18);
}
if (_coordOffset >= 15) {
_mouseCooldown = 10;
return;
}
} else {
scaledDiffY = 100 * diffY;
if (scaledDiffY - 241 * diffX <= 0) {
if (scaledDiffY - 41 * diffX <= 0) {
setDirection(18);
} else {
setDirection(15);
}
} else {
setDirection(12);
}
if (_coordOffset >= 15) {
_mouseCooldown = 10;
return;
}
}
_coordOffset = _coordOffset + 4 * (rnd(100)) / 100 + _fleeSpeed;
_mouseCooldown = 10;
}
}
}
void CBeetle::setDirection(int direction) {
if (_loaded) {
if (direction == 27 || direction == 26 || direction == 25 || direction == 28) {
_directions[0] = direction;
_directions[1] = 29;
_currentDirectionIndex = 0;
_currentSequence = _sequences[direction];
_currentFrame = 0;
_index = direction;
} else if (_sequences[direction]) {
if (direction != _index) {
_currentDirectionIndex = 0;
if (direction == 24) {
_directions[0] = 24;
_coords.y = 178;
if (_coords.x >= 265) {
_directions[1] = 15;
} else {
_directions[1] = 9;
}
_currentSequence = _sequences[24];
_currentFrame = 0;
_index = _directions[1];
} else {
if (direction <= _index) {
for (int i = _index - 1; i > direction; ++_currentDirectionIndex) {
_directions[_currentDirectionIndex] = i;
i -= 3;
}
} else {
for (int j = _index + 1; j < direction; ++_currentDirectionIndex) {
_directions[_currentDirectionIndex] = j;
j += 3;
}
}
_index = direction;
_directions[_currentDirectionIndex] = direction;
_currentFrame = 0;
_currentDirectionIndex = 0;
_currentSequence = _sequences[_directions[0]];
}
}
}
}
}
bool CBeetle::onTable() {
return _directions[_currentDirectionIndex] != 29;
}
bool CBeetle::click() {
if (_engine->getLogicManager()->_activeItem == kItemMatchBox &&
_engine->getLogicManager()->cathHasItem(12) &&
ABS<int16>(_engine->_cursorX - _coords.x) < 10 &&
ABS<int16>(_engine->_cursorY - _coords.y) < 10) {
return true;
}
_mouseCooldown = 0;
checkMouse();
return false;
}
void LastExpressEngine::doBeetle() {
int32 chapter = getLogicManager()->_globals[11];
if (chapter >= 2 && chapter <= 3 && !_beetle && getLogicManager()->_items[kItemBeetle].floating == 3) {
_beetle = new CBeetle(this);
}
}
void LastExpressEngine::endBeetle() {
if (_beetle) {
delete _beetle;
_beetle = nullptr;
}
}
void LastExpressEngine::positionSprite(Sprite *sprite, Common::Point coord) {
if (sprite) {
int spriteWidth = sprite->rect.right - sprite->rect.left + 1;
int spriteHeight = sprite->rect.bottom - sprite->rect.top + 1;
int widthFactor = sprite->rect.width + 2 * (-640 * sprite->rect.top - sprite->rect.left);
int heightFactor = 2 * (sprite->rect.right + 640 * sprite->rect.bottom) - sprite->rect.height;
if (coord.x > 0)
sprite->rect.left = coord.x;
if (coord.y > 0)
sprite->rect.top = coord.y;
sprite->rect.right = sprite->rect.left + spriteWidth - 1;
sprite->rect.bottom = sprite->rect.top + spriteHeight - 1;
sprite->rect.width = widthFactor + 2 * (sprite->rect.left + 640 * sprite->rect.top);
sprite->rect.height = 2 * (sprite->rect.right + 640 * sprite->rect.bottom) - heightFactor;
}
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,70 @@
/* 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_BEETLE_H
#define LASTEXPRESS_BEETLE_H
#include "lastexpress/lastexpress.h"
#include "lastexpress/helpers.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/system.h"
namespace LastExpress {
class LastExpressEngine;
struct Seq;
struct Sprite;
class CBeetle {
public:
CBeetle(LastExpressEngine *engine);
~CBeetle();
void tick();
void checkMouse();
void setDirection(int direction);
bool onTable();
bool click();
private:
LastExpressEngine *_engine = nullptr;
Seq *_sequences[30];
Seq *_currentSequence = nullptr;
int _currentFrame = 0;
int _index = 0;
int _coordOffset = 0;
Common::Point _coords;
int _directions[16];
int _currentDirectionIndex = 0;
Sprite *_frame = nullptr;
bool _loaded = false;
int _mouseCooldown = 0;
int _fleeSpeed = 0;
int _spawnCounter = 0;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_BEETLE_H

View File

@@ -0,0 +1,561 @@
/* 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/memstream.h"
namespace LastExpress {
void LastExpressEngine::doCredits() {
if (!_doCredits) {
TGAHeader creditsTextTga;
TGAHeader mapTgas[35];
byte *nextTga = nullptr;
int tgaIndex = 0;
int textHeight = 0;
int32 scrollSpeed = 0;
uint16 tgaPalette[256];
uint16 mapPalette[256];
memset(tgaPalette, 0, sizeof(tgaPalette));
_doCredits = 1;
bool oldCanDrawMouse = getGraphicsManager()->canDrawMouse();
getGraphicsManager()->setMouseDrawable(false);
getGraphicsManager()->burstMouseArea();
getGraphicsManager()->stepBG(49);
getMemoryManager()->freeFX();
getOtisManager()->wipeAllGSysInfo();
readTGAIntoMemory("credits.tga", &creditsTextTga);
if (_doCredits)
constructPalette(&creditsTextTga, tgaPalette);
for (int i = 1; _doCredits && i < ARRAYSIZE(mapTgas); i++) {
char currentTgaFilename[24];
memset(currentTgaFilename, 0, sizeof(currentTgaFilename));
Common::sprintf_s(currentTgaFilename, "map%05d.tga", i + 1);
readTGAIntoMemory(currentTgaFilename, &mapTgas[i]);
handleEvents();
getSoundManager()->soundThread();
}
if (_doCredits) {
tgaIndex = 0;
textHeight = creditsTextTga.height - 355;
nextTga = (creditsTextTga.rawDataPtr + 3 * creditsTextTga.colorMapLength + creditsTextTga.idLength + 18);
Slot *cacheSlot = getSoundManager()->_soundCache;
while (cacheSlot) {
if (cacheSlot->hasTag(kSoundTagLink))
break;
cacheSlot = cacheSlot->getNext();
}
if (cacheSlot) {
scrollSpeed = (textHeight << 16) / (2 * cacheSlot->getBlockCount()); // Scroll speed based on music length
} else {
_doCredits = 0;
}
}
if (_doCredits) {
_savedMouseEventHandle = getMessageManager()->getEventHandle(1);
_savedTimerEventHandle = getMessageManager()->getEventHandle(3);
getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::creditsMouseWrapper);
getMessageManager()->setEventHandle(kEventChannelTimer, &LastExpressEngine::creditsTimerWrapper);
do {
getSoundManager()->soundThread();
} while (getMessageManager()->process());
waitForTimer(5);
setEventTickInternal(false);
_savedFrameInterval = getSoundFrameCounter();
// Main loop
while (_doCredits) {
do {
getSoundManager()->soundThread();
} while (_doCredits && getMessageManager()->process());
waitForTimer(5);
if (!_savedFrameCounter) {
// Handle the background map transition
if (_doCredits == 1) {
int mapChangeRate = (322 - creditsTextTga.height) / 34;
int nextMapIndex = textHeight / mapChangeRate + 34;
// Switch to a new iteration of the map
if (nextMapIndex != tgaIndex && nextMapIndex <= 34) {
TGAHeader *currentMap = &mapTgas[nextMapIndex];
tgaIndex = nextMapIndex;
constructPalette(currentMap, mapPalette);
// 18 is the size of the original TARGA struct
byte *mapDataPtr = &currentMap->rawDataPtr[18] + 3 * currentMap->colorMapLength + currentMap->idLength;
// Draw the map to the screen
if (getGraphicsManager()->acquireSurface()) {
PixMap *screenPtr = getGraphicsManager()->_backBuffer + 20496;
byte *surfacePtr = (byte *)getGraphicsManager()->_screenSurface.getPixels() + 40992;
for (int row = 0; row < currentMap->height; row++) {
int remainingWidth = currentMap->width;
// Decompress the RLE image
while (remainingWidth > 0) {
byte controlByte = *mapDataPtr;
byte runLength = (controlByte & 0x7F) + 1;
remainingWidth -= runLength;
if (remainingWidth < 0) {
abortCredits();
break;
}
mapDataPtr++;
if ((controlByte & 0x80) == 0) { // Non-repeated sequence
for (int i = 0; i < runLength; i++) {
if (*mapDataPtr)
*screenPtr = mapPalette[*mapDataPtr];
screenPtr++;
mapDataPtr++;
}
} else { // Repeated sequence
if (*mapDataPtr) { // Non-transparent pixels
uint16 paletteValue = mapPalette[*mapDataPtr];
for (int i = 0; i < runLength; i++) {
*screenPtr++ = paletteValue;
}
} else { // Transparent pixels (skip)
screenPtr += runLength;
}
mapDataPtr++;
}
}
// Copy to surface
memcpy(surfacePtr, &screenPtr[-currentMap->width], 2 * currentMap->width);
surfacePtr += 1280;
screenPtr += 640 - currentMap->width;
}
getGraphicsManager()->unlockSurface();
}
_doCredits = 2; // Signal that a map change occurred
}
}
// Calculate scroll position for credits text
int scrollPosition = ((scrollSpeed * (getSoundFrameCounter() - _savedFrameInterval)) >> 16) - creditsTextTga.height + textHeight + 355;
// Scroll the credits...
if (scrollPosition >= 1) {
if (scrollPosition > textHeight)
scrollPosition = textHeight;
byte *textTgaData = nextTga;
// Draw credits text to screen...
if (getGraphicsManager()->acquireSurface()) {
PixMap *surfacePtr = (PixMap *)((byte *)getGraphicsManager()->_screenSurface.getPixels() + 79920);
int rowCounter = 355;
PixMap *screenPtr = getGraphicsManager()->_backBuffer + 39960;
while (rowCounter > 0) {
memcpy(surfacePtr, screenPtr, 640);
int remainingWidth = 320;
// RLE decompression of credits text
while (remainingWidth > 0) {
byte controlByte = *textTgaData;
byte runLength = (controlByte & 0x7F) + 1;
remainingWidth -= runLength;
if (remainingWidth < 0) {
abortCredits();
break;
}
textTgaData++;
if ((controlByte & 0x80) == 0) { // Non-repeated sequence
for (int i = 0; i < runLength; i++) {
byte colorIndex = *textTgaData;
if (colorIndex) {
// Apply brightness/fade at top and bottom of screen
if (rowCounter <= 339) {
if (rowCounter > 16)
*surfacePtr = tgaPalette[colorIndex];
else
*surfacePtr = (getGraphicsManager()->_brightnessData[4 - ((rowCounter - 1) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((rowCounter - 1) >> 2));
} else {
*surfacePtr = (getGraphicsManager()->_brightnessData[4 - ((355 - rowCounter) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((355 - rowCounter) >> 2));
}
}
++surfacePtr;
++textTgaData;
}
} else { // Repeated sequence
byte colorIndex = *textTgaData;
if (colorIndex) {
// Apply brightness/fade at top and bottom of screen
uint16 paletteValue;
if (rowCounter <= 339) {
if (rowCounter > 16)
paletteValue = tgaPalette[colorIndex];
else
paletteValue = (getGraphicsManager()->_brightnessData[4 - ((rowCounter - 1) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((rowCounter - 1) >> 2));
} else {
paletteValue = (getGraphicsManager()->_brightnessData[4 - ((355 - rowCounter) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((355 - rowCounter) >> 2));
}
for (int i = 0; i < runLength; i++) {
*surfacePtr++ = paletteValue;
}
} else { // Transparent pixels (skip)
surfacePtr += runLength;
}
++textTgaData;
}
}
screenPtr += 640;
surfacePtr += 320;
--rowCounter;
}
getGraphicsManager()->unlockSurface();
}
// Blit stuff to screen
if (_doCredits == 2) {
getGraphicsManager()->burstBox(
getGraphicsManager()->_renderBox1.x,
getGraphicsManager()->_renderBox1.y,
getGraphicsManager()->_renderBox1.width,
getGraphicsManager()->_renderBox1.height
);
_doCredits = 1;
} else {
getGraphicsManager()->burstBox(280, 62, 320, 355);
}
textHeight -= scrollPosition;
// Check if we have more text to display
if (textHeight > 0) {
for (int i = 0; i < scrollPosition && textHeight > 0; i++) {
int remainingWidth = 320;
while (remainingWidth > 0) {
byte controlByte = *nextTga;
byte runLength = (controlByte & 0x7F) + 1;
remainingWidth -= runLength;
if (remainingWidth < 0) {
abortCredits();
break;
}
nextTga++;
if ((controlByte & 0x80) == 0) // Non-repeated sequence
nextTga += runLength;
else // Repeated sequence
nextTga++;
}
}
} else {
abortCredits();
}
}
}
}
// Clean-up and exit...
getGraphicsManager()->setMouseDrawable(oldCanDrawMouse);
getGraphicsManager()->burstMouseArea();
Slot *cacheSlot = getSoundManager()->_soundCache;
if (cacheSlot) {
do {
if (cacheSlot->getTag() == kSoundTagLink)
break;
cacheSlot = cacheSlot->getNext();
} while (cacheSlot);
if (cacheSlot && cacheSlot->getBlockCount())
cacheSlot->setFade(0);
}
getLogicManager()->fadeToBlack();
if (nextTga)
getMemoryManager()->lockFX();
getMessageManager()->setEventHandle(kEventChannelMouse, _savedMouseEventHandle);
getMessageManager()->setEventHandle(kEventChannelTimer, _savedTimerEventHandle);
_doCredits = 0;
if (creditsTextTga.rawDataPtr) {
free(creditsTextTga.rawDataPtr);
creditsTextTga.rawDataPtr = nullptr;
}
for (int i = 0; i < 35; i++) {
if (mapTgas[i].rawDataPtr) {
free(mapTgas[i].rawDataPtr);
mapTgas[i].rawDataPtr = nullptr;
}
}
}
}
}
void LastExpressEngine::abortCredits() {
_doCredits = 0;
}
void LastExpressEngine::creditsMouse(Event *event) {
_cursorX = event->x;
_cursorY = event->y;
mouseSetRightClicked(false);
if ((event->flags & kMouseFlagLeftDown) != 0)
_savedFrameCounter = getSoundFrameCounter();
if (_savedFrameCounter && (event->flags & kMouseFlagLeftUp) != 0) {
_savedFrameInterval += getSoundFrameCounter() - _savedFrameCounter;
_savedFrameCounter = 0;
}
if ((event->flags & kMouseFlagRightDown) != 0)
abortCredits();
}
void LastExpressEngine::creditsTimer(Event *event) {
setEventTickInternal(false);
}
int32 LastExpressEngine::readTGAIntoMemory(const char *filename, TGAHeader *tgaHeader) {
int32 fileSize = 0;
HPF *archive = getArchiveManager()->openHPF(filename);
if (!archive) {
warning("Error opening file \"%s\". It probably doesn\'t exist or is write protected.", filename);
abortCredits();
return fileSize;
}
if (_doCredits) {
fileSize = archive->size * MEM_PAGE_SIZE;
byte *tgaRawData = (byte *)malloc(fileSize);
assert(tgaRawData);
getArchiveManager()->readHPF(archive, tgaRawData, archive->size);
Common::SeekableReadStream *tgaReadStream = new Common::MemoryReadStream(tgaRawData, fileSize, DisposeAfterUse::NO);
tgaHeader->idLength = tgaReadStream->readByte();
tgaHeader->colorMapType = tgaReadStream->readByte();
tgaHeader->imageType = tgaReadStream->readByte();
tgaHeader->colorMapFirstEntryIndex = tgaReadStream->readUint16LE();
tgaHeader->colorMapLength = tgaReadStream->readUint16LE();
tgaHeader->colorMapEntrySize = tgaReadStream->readByte();
tgaHeader->xOrigin = tgaReadStream->readUint16LE();
tgaHeader->yOrigin = tgaReadStream->readUint16LE();
tgaHeader->width = tgaReadStream->readUint16LE();
tgaHeader->height = tgaReadStream->readUint16LE();
tgaHeader->bitsPerPixel = tgaReadStream->readByte();
tgaHeader->imageDescriptor = tgaReadStream->readByte();
tgaHeader->rawDataPtr = tgaRawData;
delete tgaReadStream;
getArchiveManager()->closeHPF(archive);
if ((tgaHeader->imageDescriptor & 0x10) != 0)
abortCredits();
if (tgaHeader->bitsPerPixel != 8)
abortCredits();
if (!tgaHeader->colorMapType)
abortCredits();
if (tgaHeader->colorMapEntrySize != 24)
abortCredits();
if ((tgaHeader->colorMapFirstEntryIndex >= 256 || tgaHeader->colorMapLength > 256 - tgaHeader->colorMapFirstEntryIndex)) {
abortCredits();
}
}
return fileSize;
}
void LastExpressEngine::constructPalette(TGAHeader *tgaHeader, uint16 *palette) {
uint16 *paletteEntry;
byte *colorMapData = tgaHeader->rawDataPtr + 18 + tgaHeader->idLength;
int currentIndex = tgaHeader->colorMapFirstEntryIndex;
if (currentIndex + tgaHeader->colorMapLength > currentIndex) {
paletteEntry = &palette[currentIndex];
// Read BGR components (as the TGA format stores them)...
do {
// The component are reduced to 5 bits...
byte blue = colorMapData[0] >> 3;
byte green = colorMapData[1] >> 3;
byte red = colorMapData[2] >> 3;
colorMapData += 3;
// Construct 16-bit color value in RGB555 format
*paletteEntry = (red << 10) | (green << 5) | blue;
paletteEntry++;
currentIndex++;
} while (tgaHeader->colorMapLength + tgaHeader->colorMapFirstEntryIndex > currentIndex);
}
getGraphicsManager()->modifyPalette(palette, 256);
}
bool LastExpressEngine::demoEnding(bool wonGame) {
bool exitFlag = false;
int frameIndex = 0;
bool savedMouseState = getGraphicsManager()->canDrawMouse();
const char backgroundNames[35][9] = {
"CROSSING", "ABBOT", "ANCATH", "MORNING", "GUNS", "DRINKUP", "SERBSRES",
"ANALXR13", "TATIANA", "KRONAN", "CONCERT", "AUDIENCE", "COUPLE", "RUSSIANS",
"SPIES", "1017DOG", "CARRIAGE", "TYLEREGG", "TRNM2", "MAHMUD", "CATHMIL",
"FRANCY", "ONROOF", "COPS2", "MILOSVES", "KAHINGUN", "1041KISS", "EVERYONE",
"BONDAGE", "KILL", "HIGHFITE", "1315GUNS", "BOOM2", "ISTANBUL", "LASTSHOT"};
if (wonGame) {
getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::emptyHandler);
} else {
getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::demoEndingMouseWrapper);
}
getMessageManager()->setEventHandle(kEventChannelTimer, &LastExpressEngine::demoEndingTimerWrapper);
getGraphicsManager()->setMouseDrawable(false);
mouseSetRightClicked(false);
if (getGraphicsManager()->acquireSurface()) {
getGraphicsManager()->clear(getGraphicsManager()->_screenSurface, 0, 0, 640, 480);
getGraphicsManager()->unlockSurface();
}
getGraphicsManager()->burstAll();
getSoundManager()->playSoundFile("MUSSELL.SND", kSoundTypeNIS | kVolumeFull, 0, 0);
while (!exitFlag && frameIndex < 35) {
Slot *soundSlot = getSoundManager()->_soundCache;
int frameDuration = 180;
if (soundSlot) {
while (soundSlot && soundSlot->getTag() != kSoundTagNIS)
soundSlot = soundSlot->getNext();
if (soundSlot)
frameDuration = 2 * soundSlot->getBlockCount() / (35 - frameIndex);
}
int targetTime = getSoundFrameCounter() + frameDuration;
int bgResult = getArchiveManager()->loadBG(backgroundNames[frameIndex]);
if (bgResult < 0) {
exitFlag = true;
break;
}
TBM *renderBox = (bgResult == 0) ? &getGraphicsManager()->_renderBox1 : &getGraphicsManager()->_renderBox2;
getGraphicsManager()->stepDissolve(renderBox);
while (getSoundFrameCounter() < targetTime && !exitFlag) {
if (wonGame) {
if (mouseHasRightClicked()) {
exitFlag = true;
}
} else {
if (getMenu()->getEggTimerDelta())
exitFlag = true;
}
if (!exitFlag) {
bool haveMoreMessages = getMessageManager()->process();
getSoundManager()->soundThread();
getSubtitleManager()->subThread();
// Only wait and handle events if we've processed all messages, unlike the original which had a separate thread for input...
if (!haveMoreMessages)
waitForTimer(5);
}
}
frameIndex++;
}
getGraphicsManager()->setMouseDrawable(savedMouseState);
getMenu()->setEggTimerDelta(DEMO_TIMEOUT);
return exitFlag;
}
void LastExpressEngine::demoEndingMouse(Event *event) {
if (event->flags || ABS<int32>((int32)event->x - _cursorX) > 5 || ABS<int32>((int32)event->y - _cursorY) > 5)
getMenu()->setEggTimerDelta(DEMO_TIMEOUT);
}
void LastExpressEngine::demoEndingTimer(Event *event) {
setEventTickInternal(false);
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,332 @@
/* 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/game/events.h"
namespace LastExpress {
MessageManager::MessageManager(LastExpressEngine *engine) {
_engine = engine;
for (int i = 0; i < 16; i++) {
_eventHandles[i] = nullptr;
}
for (int i = 0; i < 40; i++) {
_messageHandles[i] = nullptr;
}
_autoMessages = nullptr;
}
MessageManager::~MessageManager() {
free(_autoMessages);
_autoMessages = nullptr;
}
void MessageManager::setMessageHandle(int handleChannel, void (LogicManager::*handle)(Message *)) {
_messageHandles[handleChannel] = handle;
}
void (LogicManager::*MessageManager::getMessageHandle(int handleChannel))(Message *) {
return _messageHandles[handleChannel];
}
void MessageManager::setEventHandle(int handleChannel, void (LastExpressEngine::*handle)(Event *)) {
_eventHandles[handleChannel] = handle;
}
void (LastExpressEngine::*MessageManager::getEventHandle(int handleChannel))(Event *) {
return _eventHandles[handleChannel];
}
void MessageManager::addEvent(int channel, int x, int y, int flags) {
Common::StackLock lock(*_engine->_soundMutex);
if (_numEventsInQueue >= 127)
return;
if (channel == kEventChannelTimer) {
_engine->setEventTickInternal(true);
} else if (channel == kEventChannelMouse) {
if ((flags & kMouseFlagLeftButton) != 0) {
// Originally _engine->mouseSetLeftClicked(true); was called from here,
// but it's been moved under the "if" because this lead to fake double
// clicks when registering mouse movement events (which are re-paired
// with RMOUSE and LMOUSE flags when sent to the engine via this function).
if (!_systemEventLeftMouseDown) {
_engine->mouseSetLeftClicked(true);
flags |= kMouseFlagLeftDown;
_systemEventLeftMouseDown = true;
if (_doubleClickMaxFrames + _latestTickLeftMousePressed > _engine->getSoundFrameCounter())
flags |= (kMouseFlagDoubleClick | kMouseFlagLeftDown);
_latestTickLeftMousePressed = _engine->getSoundFrameCounter();
}
} else {
if (_systemEventLeftMouseDown)
flags |= kMouseFlagLeftUp;
_systemEventLeftMouseDown = false;
}
if ((flags & kMouseFlagRightButton) != 0) {
// Originally _engine->mouseSetRightClicked(true); was called from here,
// but it's been moved under the "if" because this lead to fake double
// clicks when registering mouse movement events (which are re-paired
// with RMOUSE and LMOUSE flags when sent to the engine via this function).
if (!_systemEventRightMouseDown) {
_engine->mouseSetRightClicked(true);
flags |= kMouseFlagRightDown;
_systemEventRightMouseDown = true;
}
} else {
if (_systemEventRightMouseDown)
flags |= kMouseFlagRightUp;
_systemEventRightMouseDown = false;
}
// Originally "if (!flags)"; this tames slowdowns when dragging the mouse
// with one of the buttons pressed. Hopefully it doesn't break anything...
if (!flags || ((flags & kMouseFlagLeftButton) != 0 || (flags & kMouseFlagRightButton) != 0)) {
if (_lastEventIndex != 128)
_events[_lastEventIndex].channel = 0;
_lastEventIndex = _curEventIndex;
}
}
_events[_curEventIndex].channel = channel;
_events[_curEventIndex].x = x;
_events[_curEventIndex].y = y;
_events[_curEventIndex].flags = flags;
_curEventIndex++;
_numEventsInQueue++;
_curEventIndex &= 0x7F;
}
Event *MessageManager::getEvent() {
Event *result = nullptr;
Common::StackLock lock(*_engine->_soundMutex);
if (_nextEventIndex == _curEventIndex) {
// No events in queue...
return nullptr;
}
// Wrap around the circular buffer...
if (_lastEventIndex == _nextEventIndex) {
_lastEventIndex = 128;
}
result = &_events[_nextEventIndex];
// Update event queue indexes...
_numEventsInQueue--;
_nextEventIndex++;
_nextEventIndex &= 0x7F; // Wrap around the circular buffer...
return result;
}
void MessageManager::addMessage(int receiver, int actionId, int sender, ConsCallParam param) {
if (_numMsgsInQueue < 127) {
Message *newMsg = &_messages[_curMsgIndex];
newMsg->receiver = receiver;
newMsg->action = actionId;
newMsg->sender = sender;
newMsg->param = param;
_curMsgIndex++;
_numMsgsInQueue++;
_curMsgIndex &= 0x7F;
}
}
Message *MessageManager::getMessage() {
if (_nextMsgIndex == _curMsgIndex)
return nullptr;
Message *msg = &_messages[_nextMsgIndex];
_nextMsgIndex = (_nextMsgIndex + 1) & 0x7F;
_numMsgsInQueue--;
return msg;
}
bool MessageManager::process() {
Event *event;
event = getEvent();
if (!event || event->channel >= 16)
return false;
if (_eventHandles[event->channel])
(_engine->*_eventHandles[event->channel])(event);
return true;
}
void MessageManager::flush() {
Message *message;
do {
message = getMessage();
if (!message)
break;
if (!_engine->getLogicManager()->doAutoMessage(message)) {
if (_messageHandles[message->receiver])
(_engine->getLogicManager()->*_messageHandles[message->receiver])(message);
}
} while (message && _engine->_navigationEngineIsRunning);
}
void MessageManager::flushTime() {
for (int i = 1; i < ARRAYSIZE(_messageHandles) && _engine->_navigationEngineIsRunning; i++) {
if (_messageHandles[i])
(_engine->getLogicManager()->*_messageHandles[i])(&_emptyMessage);
}
if (_engine->_navigationEngineIsRunning)
flush();
}
void MessageManager::forceMessage(Message *msg) {
if (_messageHandles[msg->receiver])
(_engine->getLogicManager()->*_messageHandles[msg->receiver])(msg);
}
void MessageManager::clearMessageQueue() {
Common::StackLock lock(*_engine->_soundMutex);
_numMsgsInQueue = 0;
_nextMsgIndex = 0;
_curMsgIndex = 0;
}
void MessageManager::clearEventQueue() {
Common::StackLock lock(*_engine->_soundMutex);
_numEventsInQueue = 0;
_nextEventIndex = 0;
_curEventIndex = 0;
_engine->setEventTickInternal(false);
_engine->mouseSetLeftClicked(false);
_engine->mouseSetRightClicked(false);
}
void MessageManager::clearClickEvents() {
Common::StackLock lock(*_engine->_soundMutex);
if (_numEventsInQueue) {
for (int i = _nextEventIndex; _curEventIndex != i; i = ((i + 1) & 0x7F)) {
if (_events[i].channel == 1 && _events[i].flags)
_events[i].flags = 0;
}
}
}
void MessageManager::saveMessages(CVCRFile *file) {
byte *msgData = (byte *)malloc(16 * 128);
assert(msgData);
for (int i = 0; i < 128; i++) {
WRITE_LE_INT32(msgData + (i * 16) + 0, _autoMessages[i].receiver);
WRITE_LE_INT32(msgData + (i * 16) + 4, _autoMessages[i].action);
WRITE_LE_INT32(msgData + (i * 16) + 8, _autoMessages[i].sender);
WRITE_LE_INT32(msgData + (i * 16) + 12, _autoMessages[i].param.intParam);
}
file->writeRLE(msgData, 16, 128);
file->writeRLE(&_numMsgsInQueue, 4, 1);
free(msgData);
msgData = nullptr;
if (_numMsgsInQueue) {
int nextIdx = _nextMsgIndex;
while (_curMsgIndex != nextIdx) {
Message *msgToSave = &_messages[nextIdx];
msgData = (byte *)malloc(16);
assert(msgData);
WRITE_LE_INT32(msgData + 0, msgToSave->receiver);
WRITE_LE_INT32(msgData + 4, msgToSave->action);
WRITE_LE_INT32(msgData + 8, msgToSave->sender);
WRITE_LE_INT32(msgData + 12, msgToSave->param.intParam);
nextIdx = (nextIdx + 1) & 0x7F;
file->writeRLE(msgData, 16, 1);
free(msgData);
msgData = nullptr;
}
}
}
void MessageManager::loadMessages(CVCRFile *file) {
byte *msgData = (byte *)malloc(16 * 128);
assert(msgData);
file->readRLE(msgData, 16, 128);
for (int i = 0; i < 128; i++) {
_autoMessages[i].receiver = READ_LE_INT32(msgData + (i * 16) + 0);
_autoMessages[i].action = READ_LE_INT32(msgData + (i * 16) + 4);
_autoMessages[i].sender = READ_LE_INT32(msgData + (i * 16) + 8);
_autoMessages[i].param.intParam = READ_LE_INT32(msgData + (i * 16) + 12);
_autoMessages[i].param.stringParam = nullptr;
}
free(msgData);
msgData = nullptr;
int numMsgsInQueue;
file->readRLE(&numMsgsInQueue, 4, 1);
Message loadedMsg;
for (int i = 0; i < numMsgsInQueue; i++) {
msgData = (byte *)malloc(16);
assert(msgData);
file->readRLE(msgData, 16, 1);
loadedMsg.receiver = READ_LE_INT32(msgData + 0);
loadedMsg.action = READ_LE_INT32(msgData + 4);
loadedMsg.sender = READ_LE_INT32(msgData + 8);
loadedMsg.param.intParam = READ_LE_INT32(msgData + 12);
loadedMsg.param.stringParam = nullptr;
addMessage(loadedMsg.receiver, loadedMsg.action, loadedMsg.sender, loadedMsg.param);
free(msgData);
msgData = nullptr;
}
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,128 @@
/* 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_EVENTS_H
#define LASTEXPRESS_EVENTS_H
#include "lastexpress/lastexpress.h"
namespace LastExpress {
class LastExpressEngine;
class LogicManager;
struct ConsCallParam {
ConsCallParam() : intParam(0), stringParam(nullptr) {}
template<typename T>
ConsCallParam(T param) : intParam(static_cast<int32>(param)), stringParam(nullptr) {}
ConsCallParam(const char *param) : intParam(0), stringParam(param) {}
ConsCallParam(char *param) : intParam(0), stringParam(param) {}
int32 intParam;
const char *stringParam;
};
typedef struct Message {
int receiver;
int action;
int sender;
ConsCallParam param;
Message() {
clear();
}
void clear() {
receiver = 0;
action = 0;
sender = 0;
}
} Message;
typedef struct Event {
int channel;
int x;
int y;
int flags;
Event() {
channel = 0;
x = 0;
y = 0;
flags = 0;
}
} Event;
class MessageManager {
friend class LastExpressEngine;
friend class LogicManager;
friend class MemoryManager;
public:
MessageManager(LastExpressEngine *engine);
~MessageManager();
void setMessageHandle(int handleChannel, void (LogicManager::*handle)(Message *));
void (LogicManager::*getMessageHandle(int handleChannel))(Message *);
void setEventHandle(int handleChannel, void (LastExpressEngine::*handle)(Event *));
void (LastExpressEngine::*getEventHandle(int handleChannel))(Event *);
void addEvent(int channel, int x, int y, int flags);
Event *getEvent();
void addMessage(int receiver, int actionId, int sender, ConsCallParam param);
Message *getMessage();
bool process();
void flush();
void flushTime();
void forceMessage(Message *msg);
void clearMessageQueue();
void clearEventQueue();
void clearClickEvents();
void saveMessages(CVCRFile *file);
void loadMessages(CVCRFile *file);
private:
LastExpressEngine *_engine = nullptr;
Message _messages[128];
Message _emptyMessage;
void (LogicManager::*_messageHandles[40])(Message *);
int _curMsgIndex = 0;
int _numMsgsInQueue = 0;
int _nextMsgIndex = 0;
Message *_autoMessages = nullptr;
Event _events[128];
void (LastExpressEngine::*_eventHandles[16])(Event *);
int _doubleClickMaxFrames = 30;
int _latestTickLeftMousePressed = 0;
int _lastEventIndex = 128;
int _curEventIndex = 0;
int _numEventsInQueue = 0;
int _nextEventIndex = 0;
bool _systemEventRightMouseDown = false;
bool _systemEventLeftMouseDown = false;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_EVENTS_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,978 @@
/* 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/game/nis.h"
#include "lastexpress/data/archive.h"
namespace LastExpress {
NISManager::NISManager(LastExpressEngine *engine) {
_engine = engine;
_background1 = new NisSprite();
_background2 = new NisSprite();
_waneSprite = new NisSprite();
_waxSprite = new NisSprite();
}
NISManager::~NISManager() {
SAFE_DELETE(_background1);
SAFE_DELETE(_background2);
SAFE_DELETE(_waneSprite);
SAFE_DELETE(_waxSprite);
SAFE_DELETE_ARR(_events);
}
void NISManager::clearBounds() {
_nisRect.left = 640;
_nisRect.top = 480;
_nisRect.right = 0;
_nisRect.bottom = 0;
}
void NISManager::addBounds(Extent extent) {
if (_nisRect.left > (int16)extent.left)
_nisRect.left = (int16)extent.left;
if (_nisRect.right < (int16)extent.right)
_nisRect.right = (int16)extent.right;
if (_nisRect.top > (int16)extent.top)
_nisRect.top = (int16)extent.top;
if (_nisRect.bottom < (int16)extent.bottom)
_nisRect.bottom = (int16)extent.bottom;
}
void NISManager::convertNSPR16(byte *spriteData, NisSprite *outSprite) {
uint32 compDataOffset = READ_LE_UINT32(spriteData);
uint32 eraseMaskOffset = READ_LE_UINT32(spriteData + 4);
uint32 colorPaletteOffset = READ_LE_UINT32(spriteData + 8);
outSprite->rect.left = READ_LE_INT32(spriteData + 12);
outSprite->rect.top = READ_LE_INT32(spriteData + 16);
outSprite->rect.right = READ_LE_INT32(spriteData + 20);
outSprite->rect.bottom = READ_LE_INT32(spriteData + 24);
outSprite->rect.width = READ_LE_INT32(spriteData + 28);
outSprite->rect.height = READ_LE_INT32(spriteData + 32);
outSprite->colorPalette = (uint16 *)&spriteData[colorPaletteOffset];
WRITE_LE_UINT16(&outSprite->colorPalette[0], 0);
WRITE_LE_UINT16(&outSprite->colorPalette[1], 0);
for (int i = 0; i < 128; ++i)
outSprite->colorPalette[i] = READ_LE_UINT16(&outSprite->colorPalette[i]);
_engine->getGraphicsManager()->modifyPalette((uint16 *)outSprite->colorPalette, 128);
for (int i = 0; i < 128; i++)
outSprite->gammaPalette[i] = READ_UINT16(spriteData + 2 * i + 36);
outSprite->compBits = spriteData[2 * 128 + 36];
outSprite->compData = &spriteData[compDataOffset];
if (eraseMaskOffset)
outSprite->eraseMask = &spriteData[eraseMaskOffset];
}
void NISManager::getStream(byte *data, int32 size) {
if (size > _remainingStreamBytes) {
Slot *slot;
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
if (!slot->hasTag(kSoundTagNIS))
break;
}
if (slot && _currentNISSound)
_currentNISSound->addStatusFlag(kSoundFlagPauseRequested);
for (int i = 0; i < 20; ++i) {
if ((_flags & kNisFlagDataChunksAvailable) != 0)
loadChunk(32);
}
getStream(data, size);
if (_currentNISSound)
_currentNISSound->removeStatusFlag(kSoundFlagPauseRequested | kSoundFlagPaused);
} else if (size + _streamCurrentPosition <= _streamBufferSize) {
memcpy(data, (byte *)_backgroundSurface + _streamCurrentPosition, size);
_streamCurrentPosition += size;
_remainingStreamBytes -= size;
if (_streamCurrentPosition >= _streamBufferSize)
_streamCurrentPosition = 0;
} else {
int32 prevSize = _streamBufferSize - _streamCurrentPosition;
getStream(data, _streamBufferSize - _streamCurrentPosition);
getStream(&data[prevSize], size - prevSize);
}
}
void NISManager::loadSnd(int32 size) {
byte *currentBufferPtr = _currentNISSound->getCurrentBufferPtr();
byte *dataEnd = _currentNISSound->getDataEnd();
int32 availableSize = dataEnd - currentBufferPtr;
if (size < dataEnd - currentBufferPtr) {
getStream(_currentNISSound->getCurrentBufferPtr(), size);
_currentNISSound->advanceCurrentBufferPtrBy(size);
_currentNISSound->advanceLoadedBytesBy(size);
} else {
getStream(_currentNISSound->getCurrentBufferPtr(), dataEnd - currentBufferPtr);
_currentNISSound->advanceCurrentBufferPtrBy(availableSize);
_currentNISSound->setCurrentBufferPtr(_currentNISSound->getDataStart());
loadSnd(size - availableSize);
}
if (_currentNISSound->getCurrentDataPtr() < _currentNISSound->getDataStart() + 88064)
memcpy(_currentNISSound->getDataEnd(), _currentNISSound->getDataStart(), NIS_SOUND_CHUNK_SIZE);
}
int NISManager::loadChunk(int32 size) {
int32 sizeToLoad = _totalStreamPages - ((_remainingStreamBytes + 2047) / MEM_PAGE_SIZE);
if (!_archive || (_archive->status & 2) == 0)
return 0;
if (sizeToLoad > _totalStreamPages)
sizeToLoad = 0;
if (sizeToLoad > size)
sizeToLoad = size;
if (!sizeToLoad)
return 0;
uint16 sizeArch = _archive->size;
uint16 currentPos = _archive->currentPos;
if (sizeArch <= currentPos)
return 0;
if (sizeToLoad > 16)
sizeToLoad = 16;
if (currentPos + sizeToLoad > sizeArch) {
_flags ^= kNisFlagDataChunksAvailable;
sizeToLoad = sizeArch - currentPos;
}
if (sizeToLoad + _currentStreamPage >= _totalStreamPages)
sizeToLoad = _totalStreamPages - _currentStreamPage;
_engine->getArchiveManager()->readHPF(_archive, ((byte *)_backgroundSurface + (_currentStreamPage * MEM_PAGE_SIZE)), sizeToLoad);
_currentStreamPage += sizeToLoad;
_remainingStreamBytes += sizeToLoad * MEM_PAGE_SIZE;
if (_currentStreamPage >= _totalStreamPages)
_currentStreamPage -= _totalStreamPages;
return sizeToLoad;
}
bool NISManager::initNIS(const char *filename, int32 flags) {
int32 chunkSizeRead = 0;
int32 eventSize = 0;
Slot *slot;
_currentNISSound = nullptr;
_backgroundType = 0;
_selectBackgroundType = 0;
_backgroundFlag = false;
_cumulativeEventSize = 0;
_flags = flags | kNisFlagDataChunksAvailable;
_decompressToBackBuffer = true;
_firstNISBackgroundDraw = true;
clearBounds();
_currentStreamPage = 0;
_streamCurrentPosition = 0;
_remainingStreamBytes = 0;
_streamBufferSize = 1530 * MEM_PAGE_SIZE;
_originalBackgroundSurface = _engine->getGraphicsManager()->_frontBuffer;
_totalBackgroundPages = 1530;
_totalStreamPages = 1530;
_backgroundSurface = _engine->getGraphicsManager()->_frontBuffer;
_archive = _engine->getArchiveManager()->openHPF(filename);
if (!_archive) {
warning("NIS %s not ready", filename);
return false;
}
_engine->getMemoryManager()->lockSeqMem((_totalBackgroundPages - 300) * MEM_PAGE_SIZE);
getStream((byte *)&_eventsCount, 4);
_eventsCount = READ_LE_INT32(&_eventsCount);
_eventsByteStream = (byte *)(_backgroundSurface + 2);
_background1Offset = READ_LE_INT32((int32 *)_backgroundSurface + 2);
_background1Offset += 16;
_background1Offset &= 0xFFFFFFF0;
_streamBufferSize -= _background1Offset;
_background1ByteStream = (byte *)((byte *)_backgroundSurface + _streamBufferSize);
_waneSpriteOffset = READ_LE_INT32((int32 *)_backgroundSurface + 4);
_waneSpriteOffset += 16;
_waneSpriteOffset &= 0xFFFFFFF0;
_streamBufferSize -= _waneSpriteOffset;
_waneSpriteByteStream = (byte *)((byte *)_backgroundSurface + _streamBufferSize);
_streamBufferSize -= 8 * _eventsCount;
_eventsByteStream = (byte *)((byte *)_backgroundSurface + _streamBufferSize);
_totalStreamPages = (_streamBufferSize / MEM_PAGE_SIZE);
_streamBufferSize = (_streamBufferSize / MEM_PAGE_SIZE) * MEM_PAGE_SIZE;
chunkSizeRead = loadChunk(32);
getStream(_eventsByteStream, 8 * _eventsCount);
SAFE_DELETE_ARR(_events);
_events = new NisEvents[_eventsCount];
for (int i = 0; i < _eventsCount; i++) {
_events[i].eventType = READ_LE_INT16(_eventsByteStream + i * 8);
_events[i].eventTime = READ_LE_INT16(_eventsByteStream + i * 8 + 2);
_events[i].eventSize = READ_LE_INT32(_eventsByteStream + i * 8 + 4);
}
while ((_flags & kNisFlagDataChunksAvailable) != 0) {
if (!_events[4].eventSize)
break;
chunkSizeRead = loadChunk(32) * MEM_PAGE_SIZE;
if (!chunkSizeRead)
break;
eventSize = _events[4].eventSize;
_events[4].eventSize = chunkSizeRead >= eventSize ? 0 : eventSize - chunkSizeRead;
}
if ((flags & kNisFlagSoundFade) != 0)
_engine->getSoundManager()->NISFadeOut();
getNISSlot();
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
if (slot->hasTag(kSoundTagNIS))
break;
}
_currentNISSound = slot;
if (!slot) {
if ((flags & kNisFlagSoundFade) != 0)
_engine->getSoundManager()->NISFadeIn();
endNIS();
return false;
}
Common::String noExtName = filename;
noExtName.replace('.', '\0');
_currentNISSound->setSub(noExtName.c_str());
Common::String lnkName = Common::String(noExtName.c_str()) + ".LNK";
HPF *lnkResource = _engine->getArchiveManager()->openHPF(lnkName.c_str());
if (lnkResource) {
_engine->getArchiveManager()->closeHPF(lnkResource);
_chainedSoundSlot = new Slot(_engine->getSoundManager(), lnkName.c_str(), kSoundTypeLink | kVolumeFull, 105);
} else {
_chainedSoundSlot = nullptr;
}
if (_chainedSoundSlot) {
_currentNISSound->setChainedSound(_chainedSoundSlot);
_currentNISSound->addStatusFlag(kSoundFlagHasLinkAfter);
if (!scumm_stricmp(lnkName.c_str(), "1917.LNK")) {
_chainedSoundSlot->setAssociatedCharacter(kCharacterKronos);
_chainedSoundSlot->removeStatusFlag(kSoundTypeLink | kSoundFlagFixedVolume);
_chainedSoundSlot->addStatusFlag(kSoundTypeConcert);
_chainedSoundSlot->assignDirectTag(kSoundTagConcert);
_chainedSoundSlot->assignDirectVolume(16);
}
}
return true;
}
void NISManager::endNIS() {
if (_archive) {
_engine->getArchiveManager()->closeHPF(_archive);
_archive = nullptr;
}
}
void NISManager::abortNIS() {
if ((_flags & kNisFlagPlaying) != 0)
_flags |= kNisFlagAbortRequested;
}
void NISManager::nisMouse(Event *event) {
if ((event->flags & kMouseFlagRightDown) != 0)
abortNIS();
}
void NISManager::nisTimer(Event *event) {
_engine->setEventTickInternal(false);
}
void NISManager::drawSprite(NisSprite *sprite) {
Extent rect;
rect.left = sprite->rect.left;
rect.right = sprite->rect.right;
rect.top = sprite->rect.top;
rect.bottom = sprite->rect.bottom;
rect.width = sprite->rect.width;
rect.height = sprite->rect.height;
addBounds(rect);
// Use a temp sprite and just fill out the bare minimum needed for decompression...
Sprite tempSprite;
tempSprite.compData = sprite->compData;
tempSprite.colorPalette = sprite->colorPalette;
tempSprite.rect = rect;
switch (sprite->compBits) {
case 3:
if (_decompressToBackBuffer) {
_engine->getGraphicsManager()->bitBltSprite8(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
} else if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltSprite8(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 4:
if (_decompressToBackBuffer) {
_engine->getGraphicsManager()->bitBltSprite16(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
} else if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltSprite16(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 5:
if (_decompressToBackBuffer) {
_engine->getGraphicsManager()->bitBltSprite32(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
} else if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltSprite32(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 7:
if (_decompressToBackBuffer) {
_engine->getGraphicsManager()->bitBltSprite128(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
} else if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltSprite128(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 255:
if (_decompressToBackBuffer) {
_engine->getGraphicsManager()->bitBltSprite255(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
} else if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltSprite255(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
default:
return;
}
}
void NISManager::waxSprite(NisSprite *sprite) {
Extent rect;
rect.left = sprite->rect.left;
rect.right = sprite->rect.right;
rect.top = sprite->rect.top;
rect.bottom = sprite->rect.bottom;
rect.width = sprite->rect.width;
rect.height = sprite->rect.height;
addBounds(rect);
// Use a temp sprite and just fill out the bare minimum needed for decompression...
Sprite tempSprite;
tempSprite.compData = sprite->compData;
tempSprite.colorPalette = sprite->colorPalette;
tempSprite.rect = rect;
switch (sprite->compBits) {
case 3:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWax8(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 4:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWax16(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 5:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWax32(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 7:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWax128(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
default:
return;
}
}
void NISManager::waneSprite(NisSprite *sprite) {
Extent rect;
rect.left = sprite->rect.left;
rect.right = sprite->rect.right;
rect.top = sprite->rect.top;
rect.bottom = sprite->rect.bottom;
rect.width = sprite->rect.width;
rect.height = sprite->rect.height;
addBounds(rect);
// Use a temp sprite and just fill out the bare minimum needed for decompression...
Sprite tempSprite;
tempSprite.compData = sprite->compData;
tempSprite.colorPalette = sprite->colorPalette;
tempSprite.rect = rect;
switch (sprite->compBits) {
case 3:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWane8(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 4:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWane16(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 5:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWane32(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
case 7:
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->bitBltWane128(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
_engine->getGraphicsManager()->unlockSurface();
}
break;
default:
return;
}
}
void NISManager::showNISStatus(int32 status) {
if (status != -1)
_nisStatus = status;
}
bool NISManager::doNIS(const char *name, int32 flags) {
if (_engine->isGoldEdition())
return true;
Slot *slot;
if (_engine->mouseHasRightClicked())
return false;
if (!initNIS(name, flags))
return false;
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
if (slot->hasTag(kSoundTagNIS))
break;
}
if (slot) {
_currentNISSound = slot;
slot->addStatusFlag(kSoundFlagKeepAfterFinish);
}
for (int i = 0; i < 30; i++) {
loadChunk(32);
}
_savedMouseEventHandle = _engine->getMessageManager()->getEventHandle(1);
_savedTimerEventHandle = _engine->getMessageManager()->getEventHandle(3);
_engine->getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::nisMouseWrapper);
_engine->getMessageManager()->setEventHandle(kEventChannelTimer, &LastExpressEngine::nisTimerWrapper);
_engine->getSoundManager()->setSoundDriverTicks(0);
_numOfOverlays = 0;
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
if (slot->hasTag(kSoundTagNIS))
break;
}
_currentNISSound = slot;
if (slot) {
_flags |= (kNisFlagPlaying | kNisFlagHasSound);
slot->addStatusFlag(kSoundFlagKeepAfterFinish);
} else {
_flags = 0;
}
_engine->getSoundManager()->setSoundDriverTicks(0);
if (_engine->mouseHasRightClicked()) {
abortNIS();
_flags &= ~kNisFlagPlaying;
}
for (int i = 0; i < _eventsCount; i++) {
if ((_flags & kNisFlagPlaying) == 0)
break;
if (_events[i].eventTime && _events[i].eventTime + 60 < _engine->getSoundManager()->getSoundDriverTicks()) {
_engine->getSoundManager()->addSoundDriverFlags(kSoundDriverNISHasRequestedDelay);
_currentNISSound->addStatusFlag(kSoundFlagPauseRequested);
loadChunk(32);
loadChunk(32);
}
bool useDriverTicks = false;
if ((_currentNISSound->getStatusFlags() & (kSoundFlagCloseRequested | kSoundFlagClosed)) != 0 || _currentNISSound->getTime() <= 1) {
useDriverTicks = true;
} else {
_engine->getSoundManager()->setSoundDriverTicks(_currentNISSound->getTime());
}
if ((_flags & kNisFlagPlaying) != 0) {
do {
if (useDriverTicks) {
if (_events[i].eventTime <= _engine->getSoundManager()->getSoundDriverTicks())
break;
} else {
if (_events[i].eventTime <= _currentNISSound->getTime())
break;
}
if ((_flags & kNisFlagDataChunksAvailable) != 0)
loadChunk(32);
showNISStatus(-1);
if ((_currentNISSound->getStatusFlags() & kSoundFlagPaused) != 0) {
_currentNISSound->removeStatusFlag(kSoundFlagPauseRequested | kSoundFlagPaused);
_engine->getSoundManager()->removeSoundDriverFlags(kSoundDriverNISHasRequestedDelay);
}
_engine->getSoundManager()->soundThread();
_engine->getSubtitleManager()->subThread();
if (!_engine->getMessageManager()->process()) {
// Only wait and handle events if we've processed all messages, unlike the original which had a separate thread for input...
_engine->waitForTimer(5);
}
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
if (slot->hasTag(kSoundTagNIS))
break;
}
_currentNISSound = slot;
if (!_backgroundFlag && (_flags & kNisFlagAbortRequested) != 0)
_flags &= ~kNisFlagPlaying;
} while ((_flags & kNisFlagPlaying) != 0);
if ((_flags & kNisFlagPlaying) != 0) {
if (_events[i].eventTime) {
showNISStatus(_engine->getSoundManager()->getSoundDriverTicks() - _events[i].eventTime);
} else {
showNISStatus(-1);
}
processNIS(&_events[i]);
while ((_flags & kNisFlagPlaying) != 0) {
if (!_engine->getMessageManager()->process())
break;
_engine->handleEvents();
_engine->getSubtitleManager()->subThread();
_engine->getSoundManager()->soundThread();
if (!_backgroundFlag && (_flags & kNisFlagAbortRequested) != 0)
_flags &= ~kNisFlagPlaying;
}
}
}
}
if (_currentNISSound && !_currentNISSound->getTime())
_currentNISSound->addStatusFlag(kSoundFlagCloseRequested);
_engine->getMessageManager()->setEventHandle(kEventChannelMouse, _savedMouseEventHandle);
_engine->getMessageManager()->setEventHandle(kEventChannelTimer, _savedTimerEventHandle);
if (_currentNISSound && (_flags & kNisFlagAbortRequested) != 0)
_currentNISSound->addStatusFlag(kSoundFlagCloseRequested);
if (_currentNISSound) {
if (_currentNISSound->getSubtitle()) {
_currentNISSound->getSubtitle()->kill();
_engine->getSubtitleManager()->subThread();
}
_currentNISSound->removeStatusFlag(kSoundFlagKeepAfterFinish);
}
if (_chainedSoundSlot && (_chainedSoundSlot->getStatusFlags() & (kSoundFlagPlayRequested | kSoundFlagPlaying)) == 0) {
do {
_engine->getSoundManager()->soundThread();
_engine->handleEvents();
} while ((_chainedSoundSlot->getStatusFlags() & (kSoundFlagPlayRequested | kSoundFlagPlaying)) == 0);
}
endNIS();
_engine->getMemoryManager()->freeSeqMem();
_flags = 0;
if ((flags & kNisFlagSoundFade) != 0)
_engine->getSoundManager()->NISFadeIn();
return true;
}
void NISManager::processNIS(NisEvents *event) {
int32 x = _engine->getGraphicsManager()->_renderBox1.x;
int32 width = _engine->getGraphicsManager()->_renderBox1.width;
debug(2, "NISManager::processNIS(): event at time %d type %d size %d", event->eventTime, event->eventType, event->eventSize);
switch (event->eventType) {
case kNISEventBackground1: // 10
getStream(_background1ByteStream, event->eventSize);
convertNSPR16(_background1ByteStream, _background1);
if (_backgroundType == 1)
_backgroundType = 0;
return;
case kNISEventSelectBackground1: // 11
_selectBackgroundType = 1;
return;
case kNISEventBackground2: // 12
_background2ByteStream = (_background1ByteStream + ((_background1Offset - event->eventSize) & 0xFFFFFFF0));
getStream(_background2ByteStream, event->eventSize);
convertNSPR16(_background2ByteStream, _background2);
if (_backgroundType == 2)
_backgroundType = 0;
return;
case kNISEventSelectBackground2: // 13
_selectBackgroundType = 2;
return;
case kNISEventOverlay: // 20
if (!_decompressToBackBuffer) {
addBounds(_spriteExtent);
}
_waxWaneToggleFlag = !_waxWaneToggleFlag;
if (_waxWaneToggleFlag) {
_waxSpriteByteStream = (_waneSpriteByteStream + ((_waneSpriteOffset - event->eventSize) & 0xFFFFFFF0));
getStream(_waxSpriteByteStream, event->eventSize);
convertNSPR16(_waxSpriteByteStream, _waxSprite);
} else {
getStream(_waneSpriteByteStream, event->eventSize);
convertNSPR16(_waneSpriteByteStream, _waneSprite);
}
_numOfOverlays++;
return;
case kNISEventUpdate: // 21
_backgroundFlag = 0;
if (_decompressToBackBuffer)
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_backBuffer, 0, 0, 640, 480);
if (_selectBackgroundType != _backgroundType) {
drawBK(_selectBackgroundType);
} else if (_engine->getGraphicsManager()->acquireSurface()) {
if (_backgroundType == 1) {
_engine->getGraphicsManager()->copy(
_engine->getGraphicsManager()->_backBuffer,
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
_background1->rect.left,
_background1->rect.top,
_background1->rect.right - _background1->rect.left + 1,
_background1->rect.bottom - _background1->rect.top + 1
);
} else if (_backgroundType == 2) {
_engine->getGraphicsManager()->copy(
_engine->getGraphicsManager()->_backBuffer,
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
_background2->rect.left,
_background2->rect.top,
_background2->rect.right - _background2->rect.left + 1,
_background2->rect.bottom - _background2->rect.top + 1
);
}
_engine->getGraphicsManager()->unlockSurface();
}
if (!_decompressToBackBuffer) {
addBounds(_spriteExtent);
}
if (_waxWaneToggleFlag) {
if (_waxSprite->rect.left != 640)
drawSprite(_waxSprite);
_spriteExtent.left = _waxSprite->rect.left;
_spriteExtent.right = _waxSprite->rect.right;
_spriteExtent.top = _waxSprite->rect.top;
_spriteExtent.bottom = _waxSprite->rect.bottom;
_spriteExtent.width = _waxSprite->rect.width;
_spriteExtent.height = _waxSprite->rect.height;
} else {
if (_waneSprite->rect.left != 640)
drawSprite(_waneSprite);
_spriteExtent.left = _waneSprite->rect.left;
_spriteExtent.right = _waneSprite->rect.right;
_spriteExtent.top = _waneSprite->rect.top;
_spriteExtent.bottom = _waneSprite->rect.bottom;
_spriteExtent.width = _waneSprite->rect.width;
_spriteExtent.height = _waneSprite->rect.height;
}
if (_firstNISBackgroundDraw) {
_engine->getGraphicsManager()->burstBox(80, 0, 480, 480);
} else {
_engine->getGraphicsManager()->burstBox(
_nisRect.left,
_nisRect.top,
_nisRect.right - _nisRect.left + 1,
_nisRect.bottom - _nisRect.top + 1
);
}
_engine->getSoundManager()->soundThread();
_firstNISBackgroundDraw = 0;
if (_decompressToBackBuffer) {
for (int i = 0; i < 3; i++) {
_engine->getSoundManager()->soundThread();
_engine->getGraphicsManager()->dissolve((2 * x) + (2 * (i & 1)), width, 480, _engine->getGraphicsManager()->_backBuffer);
_engine->getGraphicsManager()->burstBox(x, 0, width, 480);
_engine->getSoundManager()->soundThread();
_engine->handleEvents();
}
if (_engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->copy(_engine->getGraphicsManager()->_backBuffer, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(), x, 0, width, 480);
_engine->getGraphicsManager()->unlockSurface();
}
_engine->getGraphicsManager()->burstBox(x, 0, width, 480);
_engine->getSoundManager()->soundThread();
if (_backgroundType == 1) {
drawSprite(_background1);
} else {
drawSprite(_background2);
}
_decompressToBackBuffer = 0;
_currentNISSound->play();
_engine->getSoundManager()->setSoundDriverTicks(0);
}
clearBounds();
return;
case kNISEventUpdateTransition: // 22
_backgroundFlag = 1;
if (_selectBackgroundType != _backgroundType) {
drawBK(_selectBackgroundType);
} else {
if (_engine->getGraphicsManager()->acquireSurface()) {
if (_backgroundType == 1) {
_engine->getGraphicsManager()->copy(
_engine->getGraphicsManager()->_backBuffer,
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
_background1->rect.left,
_background1->rect.top,
_background1->rect.right - _background1->rect.left + 1,
_background1->rect.bottom - _background1->rect.top + 1
);
} else if (_backgroundType == 2) {
_engine->getGraphicsManager()->copy(
_engine->getGraphicsManager()->_backBuffer,
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
_background2->rect.left,
_background2->rect.top,
_background2->rect.right - _background2->rect.left + 1,
_background2->rect.bottom - _background2->rect.top + 1
);
}
_engine->getGraphicsManager()->unlockSurface();
}
}
waxSprite(_waxSprite);
waneSprite(_waneSprite);
_engine->getGraphicsManager()->burstBox(
_nisRect.left,
_nisRect.top,
_nisRect.right - _nisRect.left + 1,
_nisRect.bottom - _nisRect.top + 1
);
_engine->getSoundManager()->soundThread();
clearBounds();
return;
case kNISEventSound1: // 30
case kNISEventSound2: // 31
_currentNISSound->setFade(event->eventSize);
return;
case kNISEventAudioData: // 32
_cumulativeEventSize += event->eventSize;
while (_currentNISSound->getSize() + NIS_SOUND_CHUNK_SIZE * _currentNISSound->getTime() + 745 <= (event->eventSize + _currentNISSound->getNumLoadedBytes()))
_engine->getSoundManager()->soundThread();
loadSnd(event->eventSize);
if ((_flags & kNisFlagSoundInitialized) == 0) {
_flags |= kNisFlagSoundInitialized;
_currentNISSound->setBlockCount(READ_LE_UINT16((uint16 *)_currentNISSound->getDataStart() + 2) - 1);
_currentNISSound->setSize(0x16000);
}
return;
default:
return;
}
}
void NISManager::drawBK(int type) {
Extent rect;
rect.left = 80;
rect.right = 559;
rect.bottom = 479;
rect.top = 0;
rect.width = 0;
rect.height = 0;
if (!_decompressToBackBuffer && _engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_screenSurface, 80, 0, 480, 480);
_engine->getGraphicsManager()->unlockSurface();
}
if (type == 1) {
drawSprite(_background1);
} else {
drawSprite(_background2);
}
_firstNISBackgroundDraw = true;
if (!_decompressToBackBuffer && _engine->getGraphicsManager()->acquireSurface()) {
_engine->getGraphicsManager()->copy((PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(), _engine->getGraphicsManager()->_backBuffer, 0, 0, 640, 480);
_engine->getGraphicsManager()->unlockSurface();
}
addBounds(rect);
_backgroundType = type;
}
void NISManager::getNISSlot() {
// This slot will automatically be included in the queue
Slot *slot = new Slot(_engine->getSoundManager(), kSoundTypeNIS | kSoundFlagCyclicBuffer | kVolumeFull, 90);
slot->setCurrentBufferPtr(slot->getSoundBuffer());
slot->setDataStart(slot->getSoundBuffer());
slot->setDataEnd(slot->getSoundBuffer() + (44 * MEM_PAGE_SIZE));
slot->setCurrentDataPtr(slot->getDataStart() + 6);
slot->setSize(44 * MEM_PAGE_SIZE);
}
Slot *NISManager::getChainedSound() {
return _chainedSoundSlot;
}
void NISManager::setChainedSound(Slot *slot) {
_chainedSoundSlot = slot;
}
int32 NISManager::getNISFlag() {
return _flags;
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,130 @@
/* 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_NIS_H
#define LASTEXPRESS_NIS_H
#include "lastexpress/lastexpress.h"
#include "lastexpress/data/archive.h"
#include "lastexpress/sound/slot.h"
namespace LastExpress {
class LastExpressEngine;
class Slot;
struct HPF;
struct Event;
struct NisSprite;
struct NisEvents;
struct Extent;
#define NIS_SOUND_CHUNK_SIZE 739
class NISManager {
public:
NISManager(LastExpressEngine *engine);
~NISManager();
void clearBounds();
void addBounds(Extent extent);
void convertNSPR16(byte *spriteData, NisSprite *outSprite);
void getStream(byte *data, int32 size);
void loadSnd(int32 size);
int loadChunk(int32 size);
bool initNIS(const char *filename, int32 flags);
void endNIS();
void abortNIS();
void nisMouse(Event *event);
void nisTimer(Event *event);
void drawSprite(NisSprite *sprite);
void waxSprite(NisSprite *sprite);
void waneSprite(NisSprite *sprite);
void showNISStatus(int32 status);
bool doNIS(const char *name, int32 flags);
void processNIS(NisEvents *event);
void drawBK(int type);
void getNISSlot();
Slot *getChainedSound();
void setChainedSound(Slot *slot);
int32 getNISFlag();
private:
LastExpressEngine *_engine = nullptr;
int32 _flags = 0;
Common::Rect _nisRect = Common::Rect(0, 0, 0, 0);
int32 _eventsCount = 0;
NisEvents *_events = nullptr;
byte *_eventsByteStream = nullptr;
int32 _numOfOverlays = 0;
HPF *_archive = nullptr;
Slot *_currentNISSound = nullptr;
Slot *_chainedSoundSlot = nullptr;
int32 _streamCurrentPosition = 0;
int32 _currentStreamPage = 0;
int32 _totalStreamPages = 0;
int32 _remainingStreamBytes = 0;
int32 _streamBufferSize = 0;
int32 _totalBackgroundPages = 0;
Extent _spriteExtent;
PixMap *_backgroundSurface = nullptr;
int32 _nisStatus = 0;
bool _decompressToBackBuffer = true;
bool _firstNISBackgroundDraw = true;
bool _waxWaneToggleFlag = false;
NisSprite *_background1 = nullptr;
NisSprite *_background2 = nullptr;
NisSprite *_waneSprite = nullptr;
NisSprite *_waxSprite = nullptr;
byte *_background1ByteStream = nullptr;
byte *_background2ByteStream = nullptr;
byte *_waneSpriteByteStream = nullptr;
byte *_waxSpriteByteStream = nullptr;
int32 _cumulativeEventSize = 0;
bool _backgroundFlag = false;
int32 _backgroundType = 0;
int32 _selectBackgroundType = 0;
int32 _background1Offset = 0;
int32 _waneSpriteOffset = 0;
PixMap *_originalBackgroundSurface = nullptr;
void (LastExpressEngine::*_savedTimerEventHandle)(Event *) = nullptr;
void (LastExpressEngine::*_savedMouseEventHandle)(Event *) = nullptr;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_NIS_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/* 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_OTIS_H
#define LASTEXPRESS_OTIS_H
#include "lastexpress/lastexpress.h"
namespace LastExpress {
class LastExpressEngine;
struct Seq;
struct Node;
class OtisManager {
public:
OtisManager(LastExpressEngine *engine);
~OtisManager() {}
void wipeLooseSprites();
void wipeGSysInfo(int character);
void wipeAllGSysInfo();
bool fDirection(int nodeIdx);
bool rDirection(int nodeIdx);
bool doorView();
bool corrRender(int nodeIdx);
bool walkingRender();
int checkMouse(int32 cursorX, int32 cursorY);
void startSeq(int character, int direction, bool loadSequence);
void getNewSeqName(int character, int direction, char *outSeqName, char *outSecondarySeqName);
void drawLooseSprites();
void refreshSequences();
int findFrame(int character, Seq *sequence, int position, bool doProcessing);
void initCurFrame(int character);
bool mainWalkTooFar(int character);
int getFudge();
void updateCharacter(int character);
void doNewSprite(int character, bool keepPreviousFrame, bool dontPlaySound);
void doSeqChange(int character);
void doNextSeq(int character);
void doNoSeq(int character);
void updateAll();
void goUpdateAll();
void adjustOtisTrueTime();
private:
LastExpressEngine *_engine = nullptr;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_OTIS_H

View File

@@ -0,0 +1,769 @@
/* 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/game/savegame.h"
#include "lastexpress/data/cvcrfile.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/menu/menu.h"
#include "lastexpress/debug.h"
#include "lastexpress/lastexpress.h"
#include "common/savefile.h"
namespace LastExpress {
SaveManager::SaveManager(LastExpressEngine *engine) {
_engine = engine;
}
void SaveManager::writeSavePoint(CVCRFile *file, int saveType, int character, int value) {
int32 originalFilePos;
int32 paddingSize;
int32 posAfterWriting;
SVCRSavePointHeader savePointHeader;
byte emptyHeader[15];
savePointHeader.type = saveType;
savePointHeader.magicNumber = 0xE660E660;
savePointHeader.size = 0;
savePointHeader.time = _engine->getLogicManager()->_gameTime;
savePointHeader.partNumber = _engine->getLogicManager()->_globals[kGlobalChapter];
savePointHeader.latestGameEvent = value;
savePointHeader.emptyField1 = 0;
savePointHeader.emptyField2 = 0;
originalFilePos = file->tell();
file->write(&savePointHeader, sizeof(SVCRSavePointHeader), 1, 0);
file->flush();
file->writeRLE(&character, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_gameTime, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_timeSpeed, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_realTime, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_activeNode, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_closeUp, 1, 1);
file->writeRLE(&_engine->getLogicManager()->_nodeReturn, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_nodeReturn2, 4, 1);
file->writeRLE(&_engine->getLogicManager()->_activeItem, 4, 1);
file->writeRLE(_engine->getLogicManager()->_blockedViews, 4, 1000);
file->writeRLE(_engine->getLogicManager()->_blockedX, 4, 16);
file->writeRLE(_engine->getLogicManager()->_softBlockedX, 4, 16);
file->writeRLE(_engine->getLogicManager()->_globals, 4, 128);
file->writeRLE(_engine->getLogicManager()->_doneNIS, 1, 512);
// Handle complex types (which were fine in the original, but not within a crossplatform context)...
byte *inventoryBuffer = (byte *)malloc(32 * 7); // 32 items, 7 bytes each
byte *doorsBuffer = (byte *)malloc(128 * 5); // 128 objects, 5 bytes each
byte *charactersBuffer = (byte *)malloc(40 * 1262); // 128 objects, 5 bytes each
assert(inventoryBuffer && doorsBuffer && charactersBuffer);
// Copy Item data...
for (int i = 0; i < 32; i++) {
int offset = i * 7;
inventoryBuffer[offset] = _engine->getLogicManager()->_items[i].mnum;
WRITE_LE_UINT16(&inventoryBuffer[offset + 1], _engine->getLogicManager()->_items[i].closeUp);
inventoryBuffer[offset + 3] = _engine->getLogicManager()->_items[i].useable;
inventoryBuffer[offset + 4] = _engine->getLogicManager()->_items[i].haveIt;
inventoryBuffer[offset + 5] = _engine->getLogicManager()->_items[i].inPocket;
inventoryBuffer[offset + 6] = _engine->getLogicManager()->_items[i].floating;
}
file->writeRLE(inventoryBuffer, 7, 32);
// Copy Door data...
for (int i = 0; i < 128; i++) {
int offset = i * 5;
doorsBuffer[offset] = _engine->getLogicManager()->_doors[i].who;
doorsBuffer[offset + 1] = _engine->getLogicManager()->_doors[i].status;
doorsBuffer[offset + 2] = _engine->getLogicManager()->_doors[i].windowCursor;
doorsBuffer[offset + 3] = _engine->getLogicManager()->_doors[i].handleCursor;
doorsBuffer[offset + 4] = _engine->getLogicManager()->_doors[i].model;
}
file->writeRLE(doorsBuffer, 5, 128);
// Copy Character data...
for (int i = 0; i < 40; i++) {
int offset = 0;
Character characterStruct = getCharacter(i);
// First copy CallParams (9 sets of 32 integers)...
for (int j = 0; j < 9; j++) {
for (int k = 0; k < 32; k++) {
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], characterStruct.callParams[j].parameters[k]);
offset += 4;
}
}
// Copy callbacks array (16 bytes)...
for (int j = 0; j < 16; j++) {
charactersBuffer[i * 1262 + offset++] = characterStruct.callbacks[j];
}
// Copy currentCall (1 byte)...
charactersBuffer[i * 1262 + offset++] = characterStruct.currentCall;
// Copy characterPosition (3 uint16)...
WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.position); offset += 2;
WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.location); offset += 2;
WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.car); offset += 2;
// Copy the remaining basic fields...
charactersBuffer[i * 1262 + offset++] = characterStruct.walkCounter;
charactersBuffer[i * 1262 + offset++] = characterStruct.attachedConductor;
charactersBuffer[i * 1262 + offset++] = characterStruct.inventoryItem;
charactersBuffer[i * 1262 + offset++] = characterStruct.direction;
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.waitedTicksUntilCycleRestart); offset += 2;
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.currentFrameSeq1); offset += 2;
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.currentFrameSeq2); offset += 2;
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.elapsedFrames); offset += 2;
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.walkStepSize); offset += 2;
charactersBuffer[i * 1262 + offset++] = characterStruct.clothes;
charactersBuffer[i * 1262 + offset++] = characterStruct.position2;
charactersBuffer[i * 1262 + offset++] = characterStruct.car2;
charactersBuffer[i * 1262 + offset++] = characterStruct.doProcessEntity;
charactersBuffer[i * 1262 + offset++] = characterStruct.needsPosFudge;
charactersBuffer[i * 1262 + offset++] = characterStruct.needsSecondaryPosFudge;
charactersBuffer[i * 1262 + offset++] = characterStruct.directionSwitch;
// Copy string fields
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceName, 13); offset += 13;
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceName2, 13); offset += 13;
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceNamePrefix, 7); offset += 7;
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceNameCopy, 13); offset += 13;
// Set pointers to zero (each 4 bytes)
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // frame1
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // frame2
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence1
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence2
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence3
// At this point, offset should equal 1262!
assert(offset == 1262);
}
file->writeRLE(charactersBuffer, 1262, 40);
free(inventoryBuffer);
free(doorsBuffer);
free(charactersBuffer);
_engine->getSoundManager()->saveSoundInfo(file);
_engine->getMessageManager()->saveMessages(file);
savePointHeader.size = file->flush();
if ((savePointHeader.size & 0xF) != 0) {
memset(emptyHeader, 0, sizeof(emptyHeader));
paddingSize = ((~(savePointHeader.size & 0xFF) & 0xF) + 1);
file->write(&emptyHeader, 1, paddingSize, 0);
savePointHeader.size += paddingSize;
}
posAfterWriting = file->tell();
file->seek(originalFilePos, 0);
checkSavePointHeader(&savePointHeader);
file->write(&savePointHeader, 32, 1, false);
file->seek(posAfterWriting, 0);
}
void SaveManager::readSavePoint(CVCRFile *file, int *saveType, uint8 *character, int *saveEvent, bool skipSoundLoading) {
int latestGameEvent;
int32 originalPos;
int32 posDiff;
SVCRSavePointHeader savePointHeader;
if (saveType && character && saveEvent) {
*saveType = 1;
*character = kCharacterCath;
*saveEvent = 0;
file->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true);
if (checkSavePointHeader(&savePointHeader)) {
latestGameEvent = savePointHeader.latestGameEvent;
*saveType = savePointHeader.type;
*saveEvent = latestGameEvent;
file->flush();
originalPos = file->tell();
// The original treats the "character" arg as uint8, but then asks
// for a four bytes integer, causing a stack corruption around it.
// This is our workaround...
int32 intCharacter;
file->readRLE(&intCharacter, 4, 1);
*character = (uint8)(intCharacter & 0xFF);
if (*character >= 40) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_gameTime, 4, 1);
if (_engine->getLogicManager()->_gameTime < 1061100) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
if (_engine->getLogicManager()->_gameTime > 4941000) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_timeSpeed, 4, 1);
if (_engine->getLogicManager()->_gameTime > 4941000) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_realTime, 4, 1);
file->readRLE(&_engine->getLogicManager()->_activeNode, 4, 1);
if (_engine->getLogicManager()->_activeNode >= 2500) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_closeUp, 1, 1);
if (_engine->getLogicManager()->_closeUp > 1) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_nodeReturn, 4, 1);
if (_engine->getLogicManager()->_nodeReturn >= 2500) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_nodeReturn2, 4, 1);
if (_engine->getLogicManager()->_nodeReturn2 >= 2500) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(&_engine->getLogicManager()->_activeItem, 4, 1);
if (_engine->getLogicManager()->_activeItem >= 32) {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
file->readRLE(_engine->getLogicManager()->_blockedViews, 4, 1000);
file->readRLE(_engine->getLogicManager()->_blockedX, 4, 16);
file->readRLE(_engine->getLogicManager()->_softBlockedX, 4, 16);
file->readRLE(_engine->getLogicManager()->_globals, 4, 128);
file->readRLE(_engine->getLogicManager()->_doneNIS, 1, 512);
// Handle complex types (which were fine in the original, but not within a crossplatform context)...
byte *inventoryBuffer = (byte *)malloc(32 * 7); // 32 items, 7 bytes each
byte *doorsBuffer = (byte *)malloc(128 * 5); // 128 objects, 5 bytes each
byte *charactersBuffer = (byte *)malloc(40 * 1262); // 40 characters, 1262 bytes each
assert(inventoryBuffer && doorsBuffer && charactersBuffer);
// Read data from file
file->readRLE(inventoryBuffer, 7, 32);
file->readRLE(doorsBuffer, 5, 128);
file->readRLE(charactersBuffer, 1262, 40);
// Copy Item data from buffer to structures
for (int i = 0; i < 32; i++) {
int offset = i * 7;
_engine->getLogicManager()->_items[i].mnum = inventoryBuffer[offset];
_engine->getLogicManager()->_items[i].closeUp = READ_LE_UINT16(&inventoryBuffer[offset + 1]);
_engine->getLogicManager()->_items[i].useable = inventoryBuffer[offset + 3];
_engine->getLogicManager()->_items[i].haveIt = inventoryBuffer[offset + 4];
_engine->getLogicManager()->_items[i].inPocket = inventoryBuffer[offset + 5];
_engine->getLogicManager()->_items[i].floating = inventoryBuffer[offset + 6];
}
// Copy Door data from buffer to structures
for (int i = 0; i < 128; i++) {
int offset = i * 5;
_engine->getLogicManager()->_doors[i].who = doorsBuffer[offset];
_engine->getLogicManager()->_doors[i].status = doorsBuffer[offset + 1];
_engine->getLogicManager()->_doors[i].windowCursor = doorsBuffer[offset + 2];
_engine->getLogicManager()->_doors[i].handleCursor = doorsBuffer[offset + 3];
_engine->getLogicManager()->_doors[i].model = doorsBuffer[offset + 4];
}
// Copy Character data from buffer to structures
for (int i = 0; i < 40; i++) {
int offset = 0;
Character *characterStruct = &getCharacter(i);
// Copy CallParams (9 sets of 32 integers)
for (int j = 0; j < 9; j++) {
for (int k = 0; k < 32; k++) {
characterStruct->callParams[j].parameters[k] = READ_LE_UINT32(&charactersBuffer[i * 1262 + offset]);
offset += 4;
}
}
// Copy callbacks array (16 bytes)
for (int j = 0; j < 16; j++) {
characterStruct->callbacks[j] = charactersBuffer[i * 1262 + offset]; offset++;
}
// Copy currentCall (1 byte)
characterStruct->currentCall = charactersBuffer[i * 1262 + offset]; offset++;
// Copy characterPosition (3 uint16)
characterStruct->characterPosition.position = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->characterPosition.location = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->characterPosition.car = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
// Copy the remaining basic fields
characterStruct->walkCounter = charactersBuffer[i * 1262 + offset++];
characterStruct->attachedConductor = charactersBuffer[i * 1262 + offset++];
characterStruct->inventoryItem = charactersBuffer[i * 1262 + offset++];
characterStruct->direction = charactersBuffer[i * 1262 + offset++];
characterStruct->waitedTicksUntilCycleRestart = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->currentFrameSeq1 = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->currentFrameSeq2 = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->elapsedFrames = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->walkStepSize = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
characterStruct->clothes = charactersBuffer[i * 1262 + offset]; offset++;
characterStruct->position2 = charactersBuffer[i * 1262 + offset]; offset++;
characterStruct->car2 = charactersBuffer[i * 1262 + offset]; offset++;
characterStruct->doProcessEntity = charactersBuffer[i * 1262 + offset]; offset++;
characterStruct->needsPosFudge = charactersBuffer[i * 1262 + offset]; offset++;
characterStruct->needsSecondaryPosFudge = charactersBuffer[i * 1262 + offset]; offset++;
characterStruct->directionSwitch = charactersBuffer[i * 1262 + offset]; offset++;
// Copy string fields
memcpy(characterStruct->sequenceName, &charactersBuffer[i * 1262 + offset], 13); offset += 13;
memcpy(characterStruct->sequenceName2, &charactersBuffer[i * 1262 + offset], 13); offset += 13;
memcpy(characterStruct->sequenceNamePrefix, &charactersBuffer[i * 1262 + offset], 7); offset += 7;
memcpy(characterStruct->sequenceNameCopy, &charactersBuffer[i * 1262 + offset], 13); offset += 13;
// Skip pointer data...
offset += 4; // frame1
offset += 4; // frame2
offset += 4; // sequence1
offset += 4; // sequence2
offset += 4; // sequence3
// At this point, offset should equal 1262!
assert(offset == 1262);
}
free(inventoryBuffer);
free(doorsBuffer);
free(charactersBuffer);
_engine->getSoundManager()->loadSoundInfo(file, skipSoundLoading);
_engine->getMessageManager()->loadMessages(file);
_engine->getLogicManager()->_globals[kGlobalChapter] = savePointHeader.partNumber;
file->flush();
posDiff = (file->tell() - originalPos) & 0xFF;
if ((posDiff & 0xF) != 0)
file->seek(((~posDiff & 0xF) + 1), 1);
for (int i = 0; i < 40; i++) {
getCharacter(i).frame1 = nullptr;
getCharacter(i).frame2 = nullptr;
getCharacter(i).sequence1 = nullptr;
getCharacter(i).sequence2 = nullptr;
getCharacter(i).sequence3 = nullptr;
}
}
}
}
void SaveManager::validateSaveFile(bool flag) {
SVCRSavePointHeader savePointHeader;
SVCRFileHeader fileHeader;
CVCRFile *tempFile = new CVCRFile(_engine);
CVCRFile *saveFile = new CVCRFile(_engine);
bool hasValidationError = false;
if (flag) {
if (_engine->_savePointHeaders)
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
_engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem(
sizeof(SVCRSavePointHeader), _engine->_savegameFilename, kCharacterMaster
);
if (!_engine->_savePointHeaders) {
error("Out of memory");
}
_engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700;
_engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1;
}
saveFile->open(_engine->_savegameFilename, CVCRMODE_RB);
saveFile->seek(0, SEEK_END);
int fileSize = saveFile->tell();
saveFile->seek(0, SEEK_SET);
if (fileSize >= (int32)sizeof(SVCRFileHeader)) {
saveFile->read(&fileHeader, sizeof(SVCRFileHeader), 1, false, true);
if (checkFileHeader(&fileHeader)) {
if (flag) {
if (_engine->_savePointHeaders)
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
_engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem(
sizeof(SVCRSavePointHeader) * (fileHeader.numVCRGames + 1),
_engine->_savegameFilename,
kCharacterMaster
);
if (!_engine->_savePointHeaders) {
error("Out of memory");
}
_engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700;
_engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1;
}
for (int i = 0; fileSize >= (int32)sizeof(SVCRFileHeader) && i < fileHeader.numVCRGames; ++i) {
_engine->getSoundManager()->soundThread();
saveFile->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true);
if (flag) {
memcpy(&_engine->_savePointHeaders[i + 1], &savePointHeader, sizeof(savePointHeader));
}
fileSize -= sizeof(SVCRSavePointHeader);
if (fileSize >= 0) {
if (checkSavePointHeader(&savePointHeader)) {
fileSize -= savePointHeader.size;
if (fileSize >= 0) {
saveFile->seek(savePointHeader.size, SEEK_CUR);
} else {
hasValidationError = true;
}
} else {
fileSize = 0;
hasValidationError = true;
}
} else {
hasValidationError = true;
}
}
saveFile->close();
} else {
hasValidationError = true;
saveFile->close();
}
} else {
hasValidationError = true;
saveFile->close();
}
if (hasValidationError) {
saveFile->open(_engine->_savegameFilename, CVCRMODE_RB);
saveFile->seek(0, SEEK_END);
fileSize = saveFile->tell();
saveFile->seek(0, SEEK_SET);
if (fileSize < (int32)sizeof(SVCRFileHeader)) {
if (fileSize) {
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
}
saveFile->close();
_engine->getVCR()->virginSaveFile();
delete tempFile;
delete saveFile;
return;
}
saveFile->read(&fileHeader, sizeof(SVCRFileHeader), 1, false, true);
if (!checkFileHeader(&fileHeader)) {
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
saveFile->close();
_engine->getVCR()->virginSaveFile();
delete tempFile;
delete saveFile;
return;
}
if (flag) {
if (_engine->_savePointHeaders)
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
_engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem(
sizeof(SVCRSavePointHeader) * (fileHeader.numVCRGames + 1),
_engine->_savegameFilename,
kCharacterMaster
);
if (!_engine->_savePointHeaders) {
error("Out of memory");
}
_engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700;
_engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1;
}
int offset = sizeof(SVCRFileHeader);
tempFile->open("temp.egg", CVCRMODE_WB);
tempFile->seek(sizeof(SVCRFileHeader), 0);
tempFile->close();
fileHeader.realWritePos = sizeof(SVCRFileHeader);
fileHeader.nextWritePos = sizeof(SVCRFileHeader);
fileHeader.lastIsTemporary = 0;
int numSavePoints = 0;
for (int j = 0; true; j++) {
if (fileSize < (int32)sizeof(SVCRFileHeader) || j >= fileHeader.numVCRGames) {
saveFile->close();
fileHeader.numVCRGames = numSavePoints;
tempFile->open("temp.egg", CVCRMODE_RWB);
tempFile->seek(0, SEEK_SET);
tempFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, false);
tempFile->seek(offset, SEEK_SET);
tempFile->close();
if (removeSavegame(_engine->_savegameFilename)) {
error("Error deleting file \"%s\"", _engine->_savegameFilename);
} else if (renameSavegame("temp.egg", _engine->_savegameFilename)) {
error("Error renaming file \"%s\" to \"%s\"", "temp.egg", _engine->_savegameFilename);
}
delete tempFile;
delete saveFile;
return;
}
_engine->getSoundManager()->soundThread();
saveFile->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true);
if (flag) {
memcpy(&_engine->_savePointHeaders[j + 1], &savePointHeader, sizeof(savePointHeader));
}
fileSize -= sizeof(SVCRSavePointHeader);
if (fileSize < 0)
break;
if (checkSavePointHeader(&savePointHeader)) {
fileSize -= savePointHeader.size;
if (fileSize < 0)
break;
numSavePoints++;
byte *tempMem = (byte *)malloc(savePointHeader.size);
saveFile->read(tempMem, savePointHeader.size, 1, false, true);
tempFile->open("temp.egg", CVCRMODE_RWB);
tempFile->seek(offset, SEEK_SET);
if (savePointHeader.type != 3 && savePointHeader.type != 4)
fileHeader.realWritePos = offset;
tempFile->write(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false);
tempFile->write(tempMem, savePointHeader.size, 1, false);
tempFile->close();
free(tempMem);
offset += savePointHeader.size + sizeof(SVCRSavePointHeader);
if (savePointHeader.type == 3 || savePointHeader.type == 4) {
fileHeader.lastIsTemporary = 1;
} else {
fileHeader.lastIsTemporary = 0;
fileHeader.nextWritePos = offset;
}
} else {
fileSize = 0;
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
}
}
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
}
delete tempFile;
delete saveFile;
}
bool SaveManager::checkFileHeader(SVCRFileHeader *fileHeader) {
if (fileHeader->magicNumber == 0x12001200 &&
fileHeader->numVCRGames >= 0 &&
fileHeader->nextWritePos >= (int32)sizeof(SVCRFileHeader) &&
fileHeader->realWritePos >= (int32)sizeof(SVCRFileHeader) &&
fileHeader->lastIsTemporary < 2 &&
fileHeader->brightness <= 6 &&
fileHeader->volume < 8) {
if (fileHeader->saveVersion == 9) {
return true;
} else {
error("Save game file \"%s\" is incompatible with this version of the game", _engine->_savegameFilename);
return false;
}
} else {
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
return false;
}
}
bool SaveManager::checkSavePointHeader(SVCRSavePointHeader *savePointHeader) {
if ((uint32)savePointHeader->magicNumber == 0xE660E660) {
if (savePointHeader->type > 0 && savePointHeader->type <= 5) {
if (savePointHeader->time >= 1061100 && savePointHeader->time <= 4941000) {
if (savePointHeader->size > 0 && (savePointHeader->size & 0xF) == 0 && savePointHeader->partNumber > 0)
return true;
}
}
}
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
return false;
}
void SaveManager::continueGame() {
uint8 character;
int saveEvent;
int saveType;
SVCRFileHeader header;
_engine->_savegame->open(_engine->_savegameFilename, CVCRMODE_RB);
_engine->_savegame->read(&header, sizeof(SVCRFileHeader), 1, false, true);
if (checkFileHeader(&header)) {
_engine->_savegame->seek(header.realWritePos, SEEK_SET);
readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, header.lastIsTemporary);
_engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime;
if (header.lastIsTemporary) {
_engine->getSoundManager()->destroyAllSound();
readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, 0);
}
_engine->_savegame->close();
_engine->getOtisManager()->wipeAllGSysInfo();
_engine->getLogicManager()->CONS_All(false, character);
} else {
_engine->_savegame->close();
}
}
void SaveManager::startRewoundGame() {
SVCRFileHeader header;
SVCRSavePointHeader savePointHeader;
int saveEvent;
int saveType;
uint8 character = 0;
CVCRFile *saveFile = new CVCRFile(_engine);
byte *buf = (byte *)malloc(0x2000);
_engine->_fightSkipCounter = 0;
_engine->_savegame->open(_engine->_savegameNames[_engine->_currentGameFileColorId], CVCRMODE_RB);
_engine->_savegameFilename = _engine->_savegameTempNames[_engine->_currentGameFileColorId];
saveFile->open(_engine->_savegameFilename, CVCRMODE_WB);
header.nextWritePos = sizeof(SVCRFileHeader);
header.numVCRGames = _engine->_currentSavePoint;
header.realWritePos = sizeof(SVCRFileHeader);
header.magicNumber = 0x12001200;
header.lastIsTemporary = 0;
header.brightness = _engine->getGraphicsManager()->getGammaLevel();
header.volume = _engine->getSoundManager()->getMasterVolume();
header.saveVersion = 9;
saveFile->write(&header, sizeof(header), 1, false);
_engine->_savegame->seek(sizeof(header), SEEK_SET);
if (_engine->_currentSavePoint > 1) {
int count = _engine->_currentSavePoint - 1;
do {
_engine->_savegame->read(&savePointHeader, sizeof(savePointHeader), 1, false, true);
saveFile->write(&savePointHeader, sizeof(savePointHeader), 1, false);
for (; savePointHeader.size > 0x2000; savePointHeader.size -= 0x2000) {
_engine->_savegame->read(buf, 0x2000, 1, false, true);
saveFile->write(buf, 0x2000, 1, false);
}
_engine->_savegame->read(buf, savePointHeader.size, 1, false, true);
saveFile->write(buf, savePointHeader.size, 1, false);
--count;
} while (count);
}
free(buf);
buf = nullptr;
if (_engine->_currentSavePoint) {
readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, false);
_engine->_savegame->close();
_engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime;
header.realWritePos = saveFile->tell();
writeSavePoint(saveFile, saveType, character, saveEvent);
header.nextWritePos = saveFile->tell();
checkFileHeader(&header);
saveFile->seek(0, SEEK_SET);
saveFile->write(&header, sizeof(header), 1, false);
}
_engine->_savegame->close();
saveFile->close();
delete saveFile;
_engine->_gracePeriodIndex = _engine->_currentSavePoint;
_engine->_gracePeriodTimer = _engine->getLogicManager()->_globals[kGlobalJacket] < 2 ? 225 : 450;
if (_engine->_currentSavePoint) {
_engine->getOtisManager()->wipeAllGSysInfo();
_engine->getLogicManager()->CONS_All(false, character);
} else {
_engine->startNewGame();
}
}
bool SaveManager::fileExists(const char *filename) {
return g_system->getSavefileManager()->exists(_engine->getTargetName() + "-" + Common::String(filename));
}
bool SaveManager::removeSavegame(const char *filename) {
return !g_system->getSavefileManager()->removeSavefile(_engine->getTargetName() + "-" + Common::String(filename));
}
bool SaveManager::renameSavegame(const char *oldName, const char *newName) {
return !g_system->getSavefileManager()->renameSavefile(
_engine->getTargetName() + "-" + Common::String(oldName),
_engine->getTargetName() + "-" + Common::String(newName), false);
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,57 @@
/* 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_SAVELOAD_H
#define LASTEXPRESS_SAVELOAD_H
#include "lastexpress/lastexpress.h"
namespace LastExpress {
class LastExpressEngine;
class CVCRFile;
struct SVCRFileHeader;
struct SVCRSavePointHeader;
class SaveManager {
public:
SaveManager(LastExpressEngine *engine);
~SaveManager() {}
void writeSavePoint(CVCRFile *file, int saveType, int entityIndex, int value);
void readSavePoint(CVCRFile *savegameData, int *saveType, uint8 *entity, int *saveEvent, bool skipSoundLoading);
void validateSaveFile(bool flag);
bool checkFileHeader(SVCRFileHeader *fileHeader);
bool checkSavePointHeader(SVCRSavePointHeader *savePointHeader);
void continueGame();
void startRewoundGame();
bool fileExists(const char *filename);
bool removeSavegame(const char *filename);
bool renameSavegame(const char *oldName, const char *newName);
private:
LastExpressEngine *_engine = nullptr;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_SAVELOAD_H

View File

@@ -0,0 +1,709 @@
/* 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/cvcrfile.h"
#include "lastexpress/menu/clock.h"
#include "lastexpress/game/vcr.h"
#include "common/config-manager.h"
#include "common/savefile.h"
#include "engines/metaengine.h"
namespace LastExpress {
VCR::VCR(LastExpressEngine *engine) {
_engine = engine;
}
void VCR::virginSaveFile() {
CVCRFile *saveFile = new CVCRFile(_engine);
SVCRFileHeader fileHeader;
assert(_engine->_savegameFilename);
fileHeader.magicNumber = 0x12001200;
fileHeader.numVCRGames = 0;
fileHeader.nextWritePos = sizeof(SVCRFileHeader);
fileHeader.realWritePos = sizeof(SVCRFileHeader);
fileHeader.lastIsTemporary = 0;
fileHeader.brightness = _engine->getGraphicsManager()->getGammaLevel();
fileHeader.saveVersion = 9;
fileHeader.volume = _engine->_soundMan->getMasterVolume();
saveFile->open(_engine->_savegameFilename, CVCRMODE_WB);
saveFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, 0);
saveFile->close();
delete saveFile;
}
void VCR::writeSavePoint(int type, int entity, int event) {
SVCRSavePointHeader savePointHeader;
SVCRFileHeader fileHeader;
CVCRFile *saveFile = new CVCRFile(_engine);
if (_engine->getLogicManager()->_activeNode <= 30) {
delete saveFile;
return;
}
if (_engine->_savegameTempNames[_engine->_currentGameFileColorId] == _engine->_savegameFilename && !_engine->_gracePeriodTimer) {
makePermanent();
}
saveFile->open(_engine->_savegameFilename, CVCRMODE_RWB);
saveFile->read(&fileHeader, 32, 1, 0, 1);
if (!_engine->getSaveManager()->checkFileHeader(&fileHeader)) {
saveFile->close();
delete saveFile;
return;
}
saveFile->seek(fileHeader.nextWritePos, 0);
if (fileHeader.numVCRGames > 0) {
saveFile->seek(fileHeader.realWritePos, 0);
saveFile->read(&savePointHeader, 32, 1, 0, 1);
if ((!_engine->getSaveManager()->checkSavePointHeader(&savePointHeader)) ||
(savePointHeader.time > _engine->getLogicManager()->_gameTime) ||
(type == 5 && savePointHeader.time == _engine->getLogicManager()->_gameTime)) {
saveFile->close();
delete saveFile;
return;
}
saveFile->seek(fileHeader.nextWritePos, 0);
if ((type == 1 || type == 2) && savePointHeader.type == 5 && (_engine->getLogicManager()->_gameTime - savePointHeader.time) < 2700) {
saveFile->seek(fileHeader.realWritePos, 0);
fileHeader.numVCRGames--;
}
}
if (type != 3 && type != 4)
fileHeader.realWritePos = saveFile->tell();
_engine->getSaveManager()->writeSavePoint(saveFile, type, entity, event);
if (!fileHeader.lastIsTemporary)
++fileHeader.numVCRGames;
if (type == 3 || type == 4) {
fileHeader.lastIsTemporary = 1;
} else {
fileHeader.lastIsTemporary = 0;
fileHeader.nextWritePos = saveFile->tell();
_engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime;
}
_engine->getSaveManager()->checkFileHeader(&fileHeader);
saveFile->seek(0, 0);
saveFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, 0);
saveFile->close();
delete saveFile;
}
void VCR::selectFromName(const char *filename) {
// Search through valid savegame names...
for (int saveNameIndex = 0; saveNameIndex < 6; saveNameIndex++) {
Common::String curSaveName = _engine->getTargetName() + "-" + _engine->_savegameNames[saveNameIndex];
if (Common::String(filename).equalsIgnoreCase(curSaveName)) {
// Found a match!
setCurrentGameColor(saveNameIndex);
return;
}
}
}
void VCR::shuffleGames() {
int currentSlot;
// Process all save game slots
for (currentSlot = 0; currentSlot < ARRAYSIZE(_engine->_savegameNames); currentSlot++) {
const char *currentFile = _engine->_savegameNames[currentSlot];
Common::InSaveFile *saveFile = _engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(currentFile));
bool slotFilled = false;
// Check if current slot has a valid save
if (saveFile) {
if (saveFile->size() <= 32) {
delete saveFile;
// Remove invalid/corrupted save files
if (_engine->getSaveManager()->removeSavegame(currentFile) != 0) {
error("Error deleting file \"%s\"", currentFile);
}
// Also remove the timestamp
Common::String tsName = currentFile;
tsName.chop(4);
tsName = _engine->getTargetName() + "-" + tsName + ".timestamp";
if (g_system->getSavefileManager()->exists(tsName))
g_system->getSavefileManager()->removeSavefile(tsName);
} else {
slotFilled = true;
delete saveFile;
}
}
// If slot is empty, try to find a valid save to move here
if (!slotFilled && currentSlot < ARRAYSIZE(_engine->_savegameNames) - 1) {
for (const char **candidateFile = &_engine->_savegameNames[currentSlot + 1];
candidateFile < _engine->_savegameTempNames;
candidateFile++) {
Common::InSaveFile *candidateSave = _engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(*candidateFile));
if (candidateSave) {
if (candidateSave->size() > 32) {
delete candidateSave;
// Move this valid save to the empty slot
if (_engine->getSaveManager()->renameSavegame(*candidateFile, currentFile) != 0) {
error("Error renaming file \"%s\" to \"%s\"", *candidateFile, currentFile);
}
slotFilled = true;
break;
} else {
delete candidateSave;
// Remove invalid candidate files
if (_engine->getSaveManager()->removeSavegame(*candidateFile) != 0) {
error("Error deleting file \"%s\"", *candidateFile);
}
// Also remove the timestamp
Common::String tsName = *candidateFile;
tsName.chop(4);
tsName = _engine->getTargetName() + "-" + tsName + ".timestamp";
if (g_system->getSavefileManager()->exists(tsName))
g_system->getSavefileManager()->removeSavefile(tsName);
}
}
}
}
}
// Set the current game color based on the most recent save...
Common::String currentSaveName = _engine->getTargetName() + "-" + Common::String(_engine->_savegameNames[currentSlot % 6]);
if (_engine->_currentGameFileColorId == -1 || !_engine->getSaveFileManager()->exists(currentSaveName)) {
setCurrentGameColor(0); // Default color
int32 newestSaveSecs = 0;
uint32 newestSaveTime = 0;
uint32 newestSaveDate = 0;
// Find the most recently modified save file
for (int i = 0; i < 6; i++) {
Common::String tsFilename = _engine->_savegameNames[i];
tsFilename.chop(4);
tsFilename = _engine->getTargetName() + "-" + tsFilename + ".timestamp";
Common::InSaveFile *saveFile = _engine->getSaveFileManager()->openForLoading(tsFilename);
if (saveFile) {
int32 eshSecs = saveFile->readSint32LE();
ExtendedSavegameHeader esh;
if (_engine->getMetaEngine()->readSavegameHeader(saveFile, &esh)) {
if (esh.date > newestSaveDate ||
(esh.date == newestSaveDate && esh.time > newestSaveTime) ||
(esh.date == newestSaveDate && esh.time == newestSaveTime && eshSecs > newestSaveSecs)) {
newestSaveSecs = eshSecs;
newestSaveTime = esh.time;
newestSaveDate = esh.date;
setCurrentGameColor(i);
}
// SwapBytesVCR(i);
}
}
delete saveFile;
}
}
}
void VCR::setCurrentGameColor(int index) {
_engine->_currentGameFileColorId = index;
_engine->_savegameFilename = _engine->_savegameNames[index];
}
void VCR::init(bool doSaveGameFlag, int saveType, int32 time) {
int cdNum;
SVCRFileHeader header;
char path[80];
int32 chosenTime = 0;
bool flag = true;
bool writeSavePoint = false;
if (_engine->_savegame && _engine->_savegame->fileIsOpen())
_engine->_savegame->close();
if (_engine->_gracePeriodTimer) {
if (_engine->isDemo()) {
time = 0;
_engine->_gracePeriodTimer = 0;
doSaveGameFlag = false;
if (_engine->getSaveManager()->removeSavegame(_engine->_savegameFilename)) {
error("Error deleting file \"%s\"", _engine->_savegameFilename);
}
_engine->_currentSavePoint = _engine->_gracePeriodIndex;
flag = false;
_engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId];
} else {
chosenTime = 0;
if (_engine->getLogicManager()->_globals[kGlobalChapter] <= 1) {
cdNum = 1;
} else {
cdNum = (_engine->getLogicManager()->_globals[kGlobalChapter] > 3) + 2;
}
if (_engine->getArchiveManager()->isCDAvailable(cdNum, path, sizeof(path))) {
writeSavePoint = 0;
_engine->_gracePeriodTimer = 0;
if (_engine->getSaveManager()->removeSavegame(_engine->_savegameFilename)) {
error("Error deleting file \"%s\"", _engine->_savegameFilename);
}
flag = false;
_engine->_currentSavePoint = _engine->_gracePeriodIndex;
_engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId];
} else {
writeSavePoint = false;
}
}
} else {
if (_engine->_savegameFilename == _engine->_savegameTempNames[_engine->_currentGameFileColorId])
_engine->getVCR()->makePermanent();
if (!_engine->isDemo()) {
writeSavePoint = doSaveGameFlag;
chosenTime = time;
}
}
if (!_engine->getSaveManager()->fileExists(_engine->_savegameFilename))
_engine->getVCR()->virginSaveFile();
if (_engine->isDemo()) {
if (doSaveGameFlag)
_engine->getVCR()->writeSavePoint(3, kCharacterCath, 0);
} else {
if (writeSavePoint)
_engine->getVCR()->writeSavePoint(3, kCharacterCath, 0);
}
if (!_engine->_gracePeriodTimer &&
_engine->getSaveManager()->fileExists(_engine->_savegameTempNames[_engine->_currentGameFileColorId]) &&
_engine->getSaveManager()->removeSavegame(_engine->_savegameTempNames[_engine->_currentGameFileColorId])
) {
error("Error deleting file \"%s\"", _engine->_savegameTempNames[_engine->_currentGameFileColorId]);
}
_engine->getSaveManager()->validateSaveFile(true);
if (!_engine->_savegame)
_engine->_savegame = new CVCRFile(_engine);
_engine->_savegame->open(_engine->_savegameFilename, CVCRMODE_RB);
_engine->_savegame->read(&header, sizeof(SVCRFileHeader), 1, false, true);
if (!_engine->getSaveManager()->checkFileHeader(&header)) {
_engine->_savegame->close();
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
}
_engine->_lastSavePointIdInFile = header.numVCRGames;
_engine->_gameTimeOfLastSavePointInFile = _engine->_savePointHeaders[header.numVCRGames].time;
if (flag)
_engine->_currentSavePoint = _engine->_lastSavePointIdInFile;
if (!_engine->_gracePeriodTimer)
_engine->_gracePeriodIndex = 0;
if (!_engine->getLogicManager()->_globals[kGlobalChapter])
_engine->getLogicManager()->_globals[kGlobalChapter] = 1;
_engine->getLogicManager()->_gameTime = _engine->_savePointHeaders[_engine->_currentSavePoint].time;
_engine->getLogicManager()->_globals[kGlobalChapter] = _engine->_savePointHeaders[_engine->_currentSavePoint].partNumber;
if (_engine->_gameTimeOfLastSavePointInFile >= 1061100) {
_engine->getClock()->startClock(_engine->getLogicManager()->_gameTime);
if (_engine->isDemo()) {
// Demo: use modified time parameter
_engine->getVCR()->autoRewind(saveType, time);
} else {
_engine->getVCR()->autoRewind(saveType, chosenTime);
}
}
}
void VCR::autoRewind(int saveType, int32 time) {
int selectedIdx = 0;
if (time) {
switch (saveType) {
case 0:
if (_engine->_currentSavePoint <= time) {
selectedIdx = 1;
} else {
selectedIdx = _engine->_currentSavePoint - time;
}
break;
case 1:
if (time < 1061100)
break;
if (!_engine->_currentSavePoint)
return;
selectedIdx = _engine->_currentSavePoint;
do {
if (_engine->_savePointHeaders[selectedIdx].time <= time)
break;
selectedIdx--;
} while (selectedIdx);
break;
case 2:
if (!_engine->_currentSavePoint)
return;
selectedIdx = _engine->_currentSavePoint;
do {
if (_engine->_savePointHeaders[selectedIdx].latestGameEvent == time)
break;
selectedIdx--;
} while (selectedIdx);
break;
case 3:
selectedIdx = _engine->_currentSavePoint;
if (_engine->_currentSavePoint > 1) {
do {
if (_engine->_savePointHeaders[selectedIdx].latestGameEvent == time)
break;
selectedIdx--;
} while (selectedIdx != 1);
}
selectedIdx--;
break;
default:
break;
}
if (selectedIdx) {
_currentSavePointInVCR = selectedIdx;
_engine->getClock()->setClock(_engine->_savePointHeaders[selectedIdx].time);
}
}
}
void VCR::free() {
_engine->getClock()->endClock();
if (_engine->_savePointHeaders) {
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
_engine->_savePointHeaders = nullptr;
}
_engine->_savegame->close();
}
bool VCR::isVirgin(int savegameIndex) {
SVCRFileHeader header;
CVCRFile *file = new CVCRFile(_engine);
if (_engine->getSaveManager()->fileExists(_engine->_savegameNames[savegameIndex]) &&
file->open(_engine->_savegameNames[savegameIndex], CVCRMODE_RB)) {
if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) &&
_engine->getSaveManager()->checkFileHeader(&header) && header.numVCRGames > 0) {
file->close();
delete file;
return false;
}
}
file->close();
delete file;
return true;
}
bool VCR::currentEndsGame() {
SVCRSavePointHeader *header = &_engine->_savePointHeaders[_engine->_currentSavePoint];
int32 latestGameEvent = header->latestGameEvent;
return _engine->_lastSavePointIdInFile == _engine->_currentSavePoint && header->type == 2 &&
( latestGameEvent == kEventAnnaKilled
|| latestGameEvent == kEventKronosHostageAnnaNoFirebird
|| latestGameEvent == kEventKahinaPunchBaggageCarEntrance
|| latestGameEvent == kEventKahinaPunchBlue
|| latestGameEvent == kEventKahinaPunchYellow
|| latestGameEvent == kEventKahinaPunchSalon
|| latestGameEvent == kEventKahinaPunchKitchen
|| latestGameEvent == kEventKahinaPunchBaggageCar
|| latestGameEvent == kEventKahinaPunchCar
|| latestGameEvent == kEventKahinaPunchSuite4
|| latestGameEvent == kEventKahinaPunchRestaurant
|| latestGameEvent == kEventKahinaPunch
|| latestGameEvent == kEventKronosGiveFirebird
|| latestGameEvent == kEventAugustFindCorpse
|| latestGameEvent == kEventMertensBloodJacket
|| latestGameEvent == kEventMertensCorpseFloor
|| latestGameEvent == kEventMertensCorpseBed
|| latestGameEvent == kEventCoudertBloodJacket
|| latestGameEvent == kEventGendarmesArrestation
|| latestGameEvent == kEventAbbotDrinkGiveDetonator
|| latestGameEvent == kEventMilosCorpseFloor
|| latestGameEvent == kEventLocomotiveAnnaStopsTrain
|| latestGameEvent == kEventTrainStopped
|| latestGameEvent == kEventCathVesnaRestaurantKilled
|| latestGameEvent == kEventCathVesnaTrainTopKilled
|| latestGameEvent == kEventLocomotiveConductorsDiscovered
|| latestGameEvent == kEventViennaAugustUnloadGuns
|| latestGameEvent == kEventViennaKronosFirebird
|| latestGameEvent == kEventVergesAnnaDead
|| latestGameEvent == kEventTrainExplosionBridge
|| latestGameEvent == kEventKronosBringNothing );
}
bool VCR::makePermanent() {
if (_engine->getSaveManager()->removeSavegame(_engine->_savegameNames[_engine->_currentGameFileColorId])) {
error("Error deleting file \"%s\"", _engine->_savegameNames[_engine->_currentGameFileColorId]);
return false;
} else if (_engine->getSaveManager()->renameSavegame(_engine->_savegameFilename, _engine->_savegameNames[_engine->_currentGameFileColorId])) {
error("Error renaming file \"%s\" to \"%s\"", _engine->_savegameFilename, _engine->_savegameNames[_engine->_currentGameFileColorId]);
return false;
} else {
_engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId];
return true;
}
}
int VCR::switchGames() {
int index = 0;
if (!isVirgin(_engine->_currentGameFileColorId)) {
index = (_engine->_currentGameFileColorId + 1) % 6;
}
_engine->_currentGameFileColorId = index;
_engine->_savegameFilename = _engine->_savegameNames[index];
if (!_engine->getSaveManager()->fileExists(_engine->_savegameFilename))
virginSaveFile();
_engine->getLogicManager()->_gameTime = 0;
_engine->getClock()->turnOnClock(false);
if (_engine->_savePointHeaders) {
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
_engine->_savePointHeaders = nullptr;
}
_engine->_savegame->close();
storeSettings();
init(false, 0, 0);
return _engine->_currentGameFileColorId;
}
void VCR::storeSettings() {
SVCRFileHeader header;
CVCRFile *file = new CVCRFile(_engine);
file->open(_engine->_savegameFilename, CVCRMODE_RWB);
if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) == 1) {
if (!_engine->getSaveManager()->checkFileHeader(&header)) {
file->close();
delete file;
return;
}
header.brightness = _engine->getGraphicsManager()->getGammaLevel();
header.volume = _engine->getSoundManager()->getMasterVolume();
file->seek(0, SEEK_SET);
file->write(&header, sizeof(SVCRFileHeader), 1, false);
file->close();
} else {
file->close();
virginSaveFile();
}
delete file;
}
void VCR::loadSettings() {
SVCRFileHeader header;
CVCRFile *file = new CVCRFile(_engine);
if (_engine->getSaveManager()->fileExists(_engine->_savegameFilename)) {
file->open(_engine->_savegameFilename, CVCRMODE_RB);
if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) == 1) {
if (_engine->getSaveManager()->checkFileHeader(&header)) {
file->seek(0, SEEK_SET);
_engine->getGraphicsManager()->setGammaLevel(header.brightness);
_engine->getSoundManager()->setMasterVolume(header.volume);
}
file->close();
} else {
file->close();
virginSaveFile();
}
}
delete file;
}
void VCR::rewind() {
if (_engine->_currentSavePoint) {
_currentSavePointInVCR = 0;
_engine->getClock()->setClock(_engine->_savePointHeaders->time);
}
}
void VCR::forward() {
if (_engine->_lastSavePointIdInFile > _engine->_currentSavePoint) {
_currentSavePointInVCR = _engine->_lastSavePointIdInFile;
_engine->getClock()->setClock(_engine->_savePointHeaders[_engine->_lastSavePointIdInFile].time);
}
}
void VCR::stop() {
_currentSavePointInVCR = _engine->_currentSavePoint;
_engine->getClock()->stopClock(_engine->_savePointHeaders[_engine->_currentSavePoint].time);
}
void VCR::seekToTime(int32 time) {
int bestIdx = 0;
int32 minDiff = ABS<int32>(_engine->_savePointHeaders[0].time - time);
for (int i = 0; i <= _engine->_lastSavePointIdInFile; i++) {
int32 curDiff = ABS<int32>(_engine->_savePointHeaders[i].time - time);
if (curDiff < minDiff) {
minDiff = curDiff;
bestIdx = i;
}
}
_currentSavePointInVCR = bestIdx;
_engine->getClock()->setClock(_engine->_savePointHeaders[bestIdx].time);
}
void VCR::updateCurGame(int32 fromTime, int32 toTime, bool searchEntry) {
int32 minTimeDiff = 0x7FFFFFFF;
int newMenuIdx = 0;
if (toTime != fromTime) {
newMenuIdx = _engine->_currentSavePoint;
if (toTime >= fromTime) {
if (searchEntry) {
for (int idx = _engine->_currentSavePoint; idx >= 0; --idx) {
int32 gameTime = _engine->_savePointHeaders[idx].time;
if (gameTime <= fromTime && minTimeDiff >= fromTime - gameTime) {
minTimeDiff = fromTime - gameTime;
newMenuIdx = idx;
}
}
} else {
newMenuIdx = _engine->_currentSavePoint - 1;
}
} else if (searchEntry) {
for (int idx = _engine->_currentSavePoint; idx <= _engine->_lastSavePointIdInFile; ++idx) {
int32 gameTime = _engine->_savePointHeaders[idx].time;
if (gameTime >= fromTime && minTimeDiff > gameTime - fromTime) {
minTimeDiff = gameTime - fromTime;
newMenuIdx = idx;
}
}
} else {
newMenuIdx = _engine->_currentSavePoint + 1;
}
_engine->_currentSavePoint = newMenuIdx;
_engine->getMenu()->updateEgg();
}
if (_engine->_currentSavePoint == _currentSavePointInVCR &&
_engine->_savePointHeaders[newMenuIdx].partNumber != _engine->getLogicManager()->_globals[kGlobalChapter]) {
_engine->getLogicManager()->_globals[kGlobalChapter] = _engine->_savePointHeaders[_engine->_currentSavePoint].partNumber;
}
}
void VCR::go() {
free();
if (_engine->_savegameTempNames[_engine->_currentGameFileColorId] == _engine->_savegameFilename) {
if (_engine->_lastSavePointIdInFile == _engine->_currentSavePoint) {
_engine->getSaveManager()->continueGame();
return;
}
_engine->getSaveManager()->startRewoundGame();
return;
}
if (_engine->_lastSavePointIdInFile != _engine->_currentSavePoint) {
_engine->getSaveManager()->startRewoundGame();
return;
}
_engine->_gracePeriodTimer = 0;
if (_engine->_currentSavePoint) {
_engine->getSaveManager()->continueGame();
return;
}
_engine->startNewGame();
}
} // End of namespace LastExpress

View File

@@ -0,0 +1,72 @@
/* 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_VCR_H
#define LASTEXPRESS_VCR_H
#include "lastexpress/lastexpress.h"
#include "lastexpress/data/cvcrfile.h"
namespace LastExpress {
class LastExpressEngine;
class CVCRFile;
struct SVCRFileHeader;
struct SVCRSavePointHeader;
class VCR {
public:
VCR(LastExpressEngine *engine);
~VCR() {}
void virginSaveFile();
void writeSavePoint(int type, int entity, int event);
void selectFromName(const char *filename);
void shuffleGames();
void setCurrentGameColor(int index);
void init(bool doSaveGameFlag, int saveType, int32 time);
void autoRewind(int saveType, int32 time);
void free();
bool isVirgin(int savegameIndex);
bool currentEndsGame();
bool makePermanent();
int switchGames();
void storeSettings();
void loadSettings();
void rewind();
void forward();
void stop();
void seekToTime(int32 time);
void updateCurGame(int32 fromTime, int32 toTime, bool searchEntry);
void go();
protected:
LastExpressEngine *_engine;
int32 _currentSavePointInVCR = 0;
};
} // End of namespace LastExpress
#endif // LASTEXPRESS_VCR_H