Files
2026-02-02 04:50:13 +01:00

213 lines
5.9 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/archive.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/substream.h"
#include "graphics/surface.h"
#include "engines/grim/grim.h"
#include "engines/grim/localize.h"
#include "engines/grim/textobject.h"
#include "engines/grim/textsplit.h"
#include "engines/grim/movie/bink.h"
#include "video/bink_decoder.h"
#ifdef USE_BINK
namespace Grim {
MoviePlayer *CreateBinkPlayer(bool demo) {
return new BinkPlayer(demo);
}
BinkPlayer::BinkPlayer(bool demo) : MoviePlayer(), _demo(demo) {
_videoDecoder = new Video::BinkDecoder();
_subtitleIndex = _subtitles.begin();
}
bool BinkPlayer::bikCheck(Common::SeekableReadStream *stream, uint32 pos) {
stream->seek(pos);
uint32 tag = stream->readUint32BE();
return (tag & 0xFFFFFF00) == MKTAG('B', 'I', 'K', 0);
}
void BinkPlayer::deinit() {
g_grim->setMovieSubtitle(nullptr);
MoviePlayer::deinit();
}
void BinkPlayer::handleFrame() {
MoviePlayer::handleFrame();
if (!_showSubtitles || _subtitleIndex == _subtitles.end())
return;
unsigned int startFrame, endFrame, curFrame;
startFrame = _subtitleIndex->_startFrame;
endFrame = _subtitleIndex->_endFrame;
curFrame = _videoDecoder->getCurFrame();
if (startFrame <= curFrame && curFrame <= endFrame) {
if (!_subtitleIndex->active) {
TextObject *textObject = new TextObject();
textObject->setDefaults(&g_grim->_sayLineDefaults);
Color c(255, 255, 255);
textObject->setFGColor(c);
textObject->setIsSpeech();
if (g_grim->getMode() == GrimEngine::SmushMode) {
// TODO: How to center exactly and put the text exactly
// at the bottom even if there are multiple lines?
textObject->setX(640 / 2);
textObject->setY(40);
}
textObject->setText(g_localizer->localize(_subtitleIndex->_textId.c_str()), false);
g_grim->setMovieSubtitle(textObject);
_subtitleIndex->active = true;
}
} else if (endFrame < curFrame) {
if (_subtitleIndex->active) {
g_grim->setMovieSubtitle(nullptr);
_subtitleIndex->active = false;
_subtitleIndex++;
}
}
}
bool BinkPlayer::loadFile(const Common::String &filename) {
_fname = filename;
if (_demo) {
Common::String subname = filename + ".sub";
// The demo uses a .lab suffix
_fname = filename + ".lab";
bool ret = MoviePlayer::loadFile(_fname);
// Load subtitles from adjacent .sub file, if present
Common::SeekableReadStream *substream = SearchMan.createReadStreamForMember(Common::Path(subname));
if (substream) {
TextSplitter tsSub("", substream);
while (!tsSub.isEof()) {
unsigned int start, end;
char textId[256];
// extract single subtitle entry
tsSub.scanString("%d\t%d\t%s", 3, &start, &end, textId);
Subtitle st(start, end, textId);
_subtitles.push_back(st);
}
delete substream;
_subtitleIndex = _subtitles.begin();
}
return ret;
}
_fname += ".m4b";
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(Common::Path(_fname));
if (!stream) {
warning("BinkPlayer::loadFile(): Can't create stream for: %s", _fname.c_str());
return false;
}
// set the default start of the bink video in case there is no SMUSH header
uint32 startBinkPos = 0x0;
// clear existing subtitles
_subtitles.clear();
char header[6];
// read the first 5 bytes of the header
stream->read(header, 5);
header[5] = 0;
if (!strcmp(header, "SMUSH")) {
// handle SMUSH header
unsigned char smushHeader[0x2000];
// read the first part
uint32 consumed = 16;
stream->read(smushHeader, consumed);
// decode the first part
for (unsigned int i = 0; i < consumed; i++) {
smushHeader[i] ^= 0xd2;
}
Common::MemoryReadStream msStart(smushHeader, consumed);
TextSplitter tsStart("", &msStart);
// extract the length / the start of the following BINK header
tsStart.scanString("%d", 1, &startBinkPos);
assert(startBinkPos < sizeof(smushHeader));
// read the rest (5 bytes less because of the string "SMUSH" at the beginning)
stream->read(smushHeader+consumed, startBinkPos - consumed - 5);
// decode the reset
for (unsigned int i = consumed; i < startBinkPos - 5; i++) {
smushHeader[i] ^= 0xd2;
}
consumed = startBinkPos - 5;
Common::MemoryReadStream msSmush(smushHeader, consumed);
TextSplitter tsSmush("", &msSmush);
// skip the first line which contains the length
tsSmush.nextLine();
tsSmush.expectString("BEGINDATA");
while (!tsSmush.checkString("ENDOFDATA")) {
unsigned int start, end;
char textId[256];
// extract single subtitle entry
tsSmush.scanString("%d\t%d\t%s", 3, &start, &end, textId);
Subtitle st(start, end, textId);
_subtitles.push_back(st);
}
tsSmush.expectString("ENDOFDATA");
}
// set current subtitle index to the first subtitle
_subtitleIndex = _subtitles.begin();
if (!bikCheck(stream, startBinkPos)) {
warning("BinkPlayer::loadFile(): Could not find BINK header for: %s", _fname.c_str());
delete stream;
return false;
}
Common::SeekableReadStream *bink = nullptr;
bink = new Common::SeekableSubReadStream(stream, startBinkPos, stream->size(), DisposeAfterUse::YES);
return _videoDecoder->loadStream(bink);
}
} // end of namespace Grim
#endif // USE_BINK